minimax
parent
004443199a
commit
52da5b0e1d
|
|
@ -0,0 +1,938 @@
|
||||||
|
# Руководство по установке и настройке Laravel Sanctum с API авторизацией
|
||||||
|
|
||||||
|
## Описание
|
||||||
|
|
||||||
|
Данное руководство описывает процесс установки и настройки **Laravel Sanctum** для API аутентификации с использованием **wadakatu/laravel-spectrum** для генерации Swagger документации.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Содержание
|
||||||
|
|
||||||
|
1. [Установка пакетов](#1-установка-пакетов)
|
||||||
|
2. [Настройка Sanctum](#2-настройка-sanctum)
|
||||||
|
3. [Структура API](#3-структура-api)
|
||||||
|
4. [API Endpoints](#4-api-endpoints)
|
||||||
|
5. [Использование API](#5-использование-api)
|
||||||
|
6. [Swagger документация](#6-swagger-документация)
|
||||||
|
7. [Команды Artisan](#7-команды-artisan)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Установка пакетов
|
||||||
|
|
||||||
|
### 1.1 Установка Laravel Sanctum
|
||||||
|
|
||||||
|
```bash
|
||||||
|
composer require laravel/sanctum
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.2 Установка wadakatu/laravel-spectrum
|
||||||
|
|
||||||
|
```bash
|
||||||
|
composer require wadakatu/laravel-spectrum
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.3 Публикация конфигураций
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Публикация Sanctum
|
||||||
|
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
|
||||||
|
|
||||||
|
# Публикация Spectrum
|
||||||
|
php artisan vendor:publish --provider="Wadakatu\LaravelSpectrum\LaravelSpectrumServiceProvider"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.4 Запуск миграций
|
||||||
|
|
||||||
|
```bash
|
||||||
|
php artisan migrate
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Примечание:** Если таблица `personal_access_tokens` уже существует, пропустите этот шаг.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Настройка Sanctum
|
||||||
|
|
||||||
|
### 2.1 Обновление модели User
|
||||||
|
|
||||||
|
Добавьте трейт `HasApiTokens` в модель `User`:
|
||||||
|
|
||||||
|
```php
|
||||||
|
// app/Models/User.php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||||
|
use Illuminate\Notifications\Notifiable;
|
||||||
|
use Laravel\Sanctum\HasApiTokens;
|
||||||
|
|
||||||
|
class User extends Authenticatable
|
||||||
|
{
|
||||||
|
use HasApiTokens, HasFactory, Notifiable;
|
||||||
|
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 Настройка middleware
|
||||||
|
|
||||||
|
Обновите файл `bootstrap/app.php`:
|
||||||
|
|
||||||
|
```php
|
||||||
|
// bootstrap/app.php
|
||||||
|
|
||||||
|
return Application::configure(basePath: dirname(__DIR__))
|
||||||
|
->withRouting(
|
||||||
|
web: __DIR__.'/../routes/web.php',
|
||||||
|
api: __DIR__.'/../routes/api.php',
|
||||||
|
commands: __DIR__.'/../routes/console.php',
|
||||||
|
health: '/up',
|
||||||
|
)
|
||||||
|
->withMiddleware(function (Middleware $middleware): void {
|
||||||
|
$middleware->statefulApi();
|
||||||
|
})
|
||||||
|
->withExceptions(function (Exceptions $exceptions): void {
|
||||||
|
//
|
||||||
|
})->create();
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 Конфигурация Sanctum
|
||||||
|
|
||||||
|
Конфигурация находится в файле `config/sanctum.php`. Основные настройки:
|
||||||
|
|
||||||
|
```php
|
||||||
|
// config/sanctum.php
|
||||||
|
|
||||||
|
return [
|
||||||
|
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
|
||||||
|
'%s%s',
|
||||||
|
'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1',
|
||||||
|
env('APP_URL') ? ','.parse_url(env('APP_URL'), PHP_URL_HOST) : ''
|
||||||
|
))),
|
||||||
|
|
||||||
|
'guard' => ['web'],
|
||||||
|
|
||||||
|
'expiration' => null,
|
||||||
|
|
||||||
|
'token_prefix' => env('SANCTUM_TOKEN_PREFIX', ''),
|
||||||
|
|
||||||
|
'middleware' => [
|
||||||
|
'verify_csrf_token' => App\Http\Middleware\VerifyCsrfToken::class,
|
||||||
|
'encrypt_cookies' => App\Http\Middleware\EncryptCookies::class,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Структура API
|
||||||
|
|
||||||
|
### 3.1 Директории
|
||||||
|
|
||||||
|
```
|
||||||
|
app/
|
||||||
|
├── Http/
|
||||||
|
│ ├── Controllers/
|
||||||
|
│ │ └── Api/
|
||||||
|
│ │ ├── AuthController.php
|
||||||
|
│ │ └── PostController.php
|
||||||
|
│ ├── Requests/
|
||||||
|
│ │ ├── LoginRequest.php
|
||||||
|
│ │ ├── RegisterRequest.php
|
||||||
|
│ │ ├── StorePostRequest.php
|
||||||
|
│ │ └── UpdatePostRequest.php
|
||||||
|
│ └── Resources/
|
||||||
|
│ ├── PostCollection.php
|
||||||
|
│ ├── PostResource.php
|
||||||
|
│ └── UserResource.php
|
||||||
|
└── Policies/
|
||||||
|
└── PostPolicy.php
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 API Resources
|
||||||
|
|
||||||
|
**UserResource** - форматирование данных пользователя:
|
||||||
|
|
||||||
|
```php
|
||||||
|
// app/Http/Resources/UserResource.php
|
||||||
|
|
||||||
|
namespace App\Http\Resources;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
|
||||||
|
class UserResource extends JsonResource
|
||||||
|
{
|
||||||
|
public function toArray(Request $request): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'id' => $this->id,
|
||||||
|
'name' => $this->name,
|
||||||
|
'email' => $this->email,
|
||||||
|
'role' => $this->role,
|
||||||
|
'created_at' => $this->created_at,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**PostResource** - форматирование данных поста:
|
||||||
|
|
||||||
|
```php
|
||||||
|
// app/Http/Resources/PostResource.php
|
||||||
|
|
||||||
|
namespace App\Http\Resources;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
|
||||||
|
class PostResource extends JsonResource
|
||||||
|
{
|
||||||
|
public function toArray(Request $request): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'id' => $this->id,
|
||||||
|
'title' => $this->title,
|
||||||
|
'content' => $this->content,
|
||||||
|
'user_id' => $this->user_id,
|
||||||
|
'user' => new UserResource($this->whenLoaded('user')),
|
||||||
|
'created_at' => $this->created_at,
|
||||||
|
'updated_at' => $this->updated_at,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3 Form Requests
|
||||||
|
|
||||||
|
**RegisterRequest** - валидация регистрации:
|
||||||
|
|
||||||
|
```php
|
||||||
|
// app/Http/Requests/RegisterRequest.php
|
||||||
|
|
||||||
|
namespace App\Http\Requests;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class RegisterRequest extends FormRequest
|
||||||
|
{
|
||||||
|
public function authorize(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'name' => ['required', 'string', 'max:255'],
|
||||||
|
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
|
||||||
|
'password' => ['required', 'string', 'min:8', 'confirmed'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**LoginRequest** - валидация входа:
|
||||||
|
|
||||||
|
```php
|
||||||
|
// app/Http/Requests/LoginRequest.php
|
||||||
|
|
||||||
|
namespace App\Http\Requests;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class LoginRequest extends FormRequest
|
||||||
|
{
|
||||||
|
public function authorize(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'email' => ['required', 'string', 'email'],
|
||||||
|
'password' => ['required', 'string'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**StorePostRequest** - валидация создания поста:
|
||||||
|
|
||||||
|
```php
|
||||||
|
// app/Http/Requests/StorePostRequest.php
|
||||||
|
|
||||||
|
namespace App\Http\Requests;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class StorePostRequest extends FormRequest
|
||||||
|
{
|
||||||
|
public function authorize(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'title' => ['required', 'string', 'max:255'],
|
||||||
|
'content' => ['required', 'string'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**UpdatePostRequest** - валидация обновления поста:
|
||||||
|
|
||||||
|
```php
|
||||||
|
// app/Http/Requests/UpdatePostRequest.php
|
||||||
|
|
||||||
|
namespace App\Http\Requests;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class UpdatePostRequest extends FormRequest
|
||||||
|
{
|
||||||
|
public function authorize(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'title' => ['sometimes', 'string', 'max:255'],
|
||||||
|
'content' => ['sometimes', 'string'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. API Endpoints
|
||||||
|
|
||||||
|
### 4.1 Маршруты
|
||||||
|
|
||||||
|
```php
|
||||||
|
// routes/api.php
|
||||||
|
|
||||||
|
use App\Http\Controllers\Api\AuthController;
|
||||||
|
use App\Http\Controllers\Api\PostController;
|
||||||
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
|
// Swagger Documentation
|
||||||
|
Route::get('/docs', function () {
|
||||||
|
return redirect()->to('/docs/index.html');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Public routes
|
||||||
|
Route::post('/register', [AuthController::class, 'register'])->name('api.register');
|
||||||
|
Route::post('/login', [AuthController::class, 'login'])->name('api.login');
|
||||||
|
|
||||||
|
// Protected routes
|
||||||
|
Route::middleware('auth:sanctum')->group(function () {
|
||||||
|
// Auth
|
||||||
|
Route::post('/logout', [AuthController::class, 'logout'])->name('api.logout');
|
||||||
|
Route::get('/user', [AuthController::class, 'user'])->name('api.user');
|
||||||
|
|
||||||
|
// Posts
|
||||||
|
Route::get('/posts', [PostController::class, 'index'])->name('api.posts.index');
|
||||||
|
Route::post('/posts', [PostController::class, 'store'])->name('api.posts.store');
|
||||||
|
Route::get('/posts/{post}', [PostController::class, 'show'])->name('api.posts.show');
|
||||||
|
Route::put('/posts/{post}', [PostController::class, 'update'])->name('api.posts.update');
|
||||||
|
Route::delete('/posts/{post}', [PostController::class, 'destroy'])->name('api.posts.destroy');
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 Список Endpoints
|
||||||
|
|
||||||
|
| Метод | Endpoint | Описание | Авторизация |
|
||||||
|
|-------|----------|----------|-------------|
|
||||||
|
| POST | `/api/register` | Регистрация | Нет |
|
||||||
|
| POST | `/api/login` | Вход | Нет |
|
||||||
|
| POST | `/api/logout` | Выход | Да |
|
||||||
|
| GET | `/api/user` | Текущий пользователь | Да |
|
||||||
|
| GET | `/api/posts` | Список постов | Да |
|
||||||
|
| POST | `/api/posts` | Создание поста | Да |
|
||||||
|
| GET | `/api/posts/{id}` | Просмотр поста | Да |
|
||||||
|
| PUT | `/api/posts/{id}` | Обновление поста | Да |
|
||||||
|
| DELETE | `/api/posts/{id}` | Удаление поста | Да |
|
||||||
|
| GET | `/api/docs` | Swagger UI | Нет |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Использование API
|
||||||
|
|
||||||
|
### 5.1 Регистрация
|
||||||
|
|
||||||
|
**Запрос:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST http://la.test/api/register \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"name": "John Doe",
|
||||||
|
"email": "john@example.com",
|
||||||
|
"password": "password123",
|
||||||
|
"password_confirmation": "password123"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Ответ:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"message": "User registered successfully",
|
||||||
|
"user": {
|
||||||
|
"id": 1,
|
||||||
|
"name": "John Doe",
|
||||||
|
"email": "john@example.com",
|
||||||
|
"role": null,
|
||||||
|
"created_at": "2026-03-20T03:45:00.000000Z"
|
||||||
|
},
|
||||||
|
"token": "1|abc123..."
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2 Вход
|
||||||
|
|
||||||
|
**Запрос:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST http://la.test/api/login \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"email": "john@example.com",
|
||||||
|
"password": "password123"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Ответ:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"message": "Login successful",
|
||||||
|
"user": {
|
||||||
|
"id": 1,
|
||||||
|
"name": "John Doe",
|
||||||
|
"email": "john@example.com",
|
||||||
|
"role": null,
|
||||||
|
"created_at": "2026-03-20T03:45:00.000000Z"
|
||||||
|
},
|
||||||
|
"token": "2|def456..."
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.3 Создание поста
|
||||||
|
|
||||||
|
**Запрос:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST http://la.test/api/posts \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer {token}" \
|
||||||
|
-d '{
|
||||||
|
"title": "My First Post",
|
||||||
|
"content": "This is the content of my post."
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Ответ:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"message": "Post created successfully",
|
||||||
|
"post": {
|
||||||
|
"id": 1,
|
||||||
|
"title": "My First Post",
|
||||||
|
"content": "This is the content of my post.",
|
||||||
|
"user_id": 1,
|
||||||
|
"user": {
|
||||||
|
"id": 1,
|
||||||
|
"name": "John Doe",
|
||||||
|
"email": "john@example.com"
|
||||||
|
},
|
||||||
|
"created_at": "2026-03-20T03:50:00.000000Z",
|
||||||
|
"updated_at": "2026-03-20T03:50:00.000000Z"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.4 Обновление поста
|
||||||
|
|
||||||
|
**Запрос:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X PUT http://la.test/api/posts/1 \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer {token}" \
|
||||||
|
-d '{
|
||||||
|
"title": "Updated Title"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.5 Удаление поста
|
||||||
|
|
||||||
|
**Запрос:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X DELETE http://la.test/api/posts/1 \
|
||||||
|
-H "Authorization: Bearer {token}"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.6 Выход
|
||||||
|
|
||||||
|
**Запрос:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST http://la.test/api/logout \
|
||||||
|
-H "Authorization: Bearer {token}"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Swagger документация
|
||||||
|
|
||||||
|
### 6.1 Доступ к документации
|
||||||
|
|
||||||
|
Swagger UI доступен по адресу: `{APP_URL}/api/docs`
|
||||||
|
|
||||||
|
Например: `http://la.test/api/docs`
|
||||||
|
|
||||||
|
### 6.2 Структура файлов документации
|
||||||
|
|
||||||
|
```
|
||||||
|
public/
|
||||||
|
└── docs/
|
||||||
|
├── index.html # Swagger UI
|
||||||
|
└── openapi.yaml # OpenAPI спецификация
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.3 Обновление документации
|
||||||
|
|
||||||
|
После добавления новых endpointов перегенерируйте документацию:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
php artisan swagger:generate
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.4 Конфигурация Spectrum
|
||||||
|
|
||||||
|
```php
|
||||||
|
// config/spectrum.php
|
||||||
|
|
||||||
|
return [
|
||||||
|
'output' => 'public/docs',
|
||||||
|
'title' => env('APP_NAME', 'Laravel API'),
|
||||||
|
'description' => 'API Documentation',
|
||||||
|
'version' => '1.0.0',
|
||||||
|
'server_url' => env('APP_URL'),
|
||||||
|
'servers' => [
|
||||||
|
['url' => env('APP_URL'), 'description' => 'Local server'],
|
||||||
|
],
|
||||||
|
'security_schemes' => [
|
||||||
|
'bearerAuth' => [
|
||||||
|
'type' => 'http',
|
||||||
|
'scheme' => 'bearer',
|
||||||
|
'bearerFormat' => 'JWT',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'default_security_scheme' => 'bearerAuth',
|
||||||
|
'paths' => [
|
||||||
|
'controllers' => ['App\\Http\\Controllers\\Api\\'],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Команды Artisan
|
||||||
|
|
||||||
|
### 7.1 Создание пользовательской команды
|
||||||
|
|
||||||
|
#### Структура директорий
|
||||||
|
|
||||||
|
Команды Artisan располагаются в директории `app/Console/Commands/`:
|
||||||
|
|
||||||
|
```
|
||||||
|
app/
|
||||||
|
├── Console/
|
||||||
|
│ └── Commands/
|
||||||
|
│ └── GenerateSwagger.php # Наша команда
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Создание команды
|
||||||
|
|
||||||
|
**1. Создайте директорию Commands:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p app/Console/Commands
|
||||||
|
```
|
||||||
|
|
||||||
|
**2. Создайте файл команды:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# app/Console/Commands/GenerateSwagger.php
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Пример кода команды
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Support\Facades\File;
|
||||||
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
|
class GenerateSwagger extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Имя и сигнатура команды.
|
||||||
|
* Используется для вызова: php artisan swagger:generate
|
||||||
|
*/
|
||||||
|
protected $signature = 'swagger:generate';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Описание команды.
|
||||||
|
* Показывается в списке команд: php artisan list
|
||||||
|
*/
|
||||||
|
protected $description = 'Generate OpenAPI specification for the API';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Метод execute() - основная логика команды.
|
||||||
|
* Вызывается при выполнении команды.
|
||||||
|
*/
|
||||||
|
public function handle(): int
|
||||||
|
{
|
||||||
|
// Вывод информационного сообщения
|
||||||
|
$this->info('Generating OpenAPI specification...');
|
||||||
|
|
||||||
|
// Создание директории для документации
|
||||||
|
$outputDir = public_path('docs');
|
||||||
|
if (!File::exists($outputDir)) {
|
||||||
|
File::makeDirectory($outputDir, 0755, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Генерация спецификации
|
||||||
|
$spec = $this->generateSpec();
|
||||||
|
|
||||||
|
// Конвертация в YAML формат
|
||||||
|
$yaml = Yaml::dump($spec, 4, 2);
|
||||||
|
|
||||||
|
// Сохранение файла
|
||||||
|
File::put($outputDir . '/openapi.yaml', $yaml);
|
||||||
|
$this->info('OpenAPI specification saved to public/docs/openapi.yaml');
|
||||||
|
|
||||||
|
// Возврат кода успеха
|
||||||
|
return Command::SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Метод для генерации структуры OpenAPI спецификации.
|
||||||
|
* Можно создавать YAML или JSON вручную.
|
||||||
|
*/
|
||||||
|
protected function generateSpec(): array
|
||||||
|
{
|
||||||
|
$appUrl = config('app.url', 'http://localhost');
|
||||||
|
|
||||||
|
return [
|
||||||
|
'openapi' => '3.0.0',
|
||||||
|
'info' => [
|
||||||
|
'title' => config('app.name', 'Laravel API'),
|
||||||
|
'description' => 'API Documentation',
|
||||||
|
'version' => '1.0.0',
|
||||||
|
],
|
||||||
|
'servers' => [
|
||||||
|
['url' => $appUrl, 'description' => 'Local server'],
|
||||||
|
],
|
||||||
|
'paths' => $this->generatePaths(),
|
||||||
|
'components' => [
|
||||||
|
'securitySchemes' => [
|
||||||
|
'bearerAuth' => [
|
||||||
|
'type' => 'http',
|
||||||
|
'scheme' => 'bearer',
|
||||||
|
'bearerFormat' => 'JWT',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Генерация секции paths для OpenAPI.
|
||||||
|
*/
|
||||||
|
protected function generatePaths(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'/api/register' => [
|
||||||
|
'post' => [
|
||||||
|
'tags' => ['Auth'],
|
||||||
|
'summary' => 'Register a new user',
|
||||||
|
// ... описание endpoint
|
||||||
|
],
|
||||||
|
],
|
||||||
|
// ... другие endpoints
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Основные компоненты команды
|
||||||
|
|
||||||
|
| Компонент | Описание |
|
||||||
|
|----------|----------|
|
||||||
|
| `$signature` | Имя команды (например, `swagger:generate`) |
|
||||||
|
| `$description` | Описание для списка команд |
|
||||||
|
| `handle()` | Основной метод выполнения команды |
|
||||||
|
| `$this->info()` | Вывод информационного сообщения |
|
||||||
|
| `$this->error()` | Вывод сообщения об ошибке |
|
||||||
|
| `$this->warn()` | Вывод предупреждения |
|
||||||
|
| `$this->question()` | Вывод вопроса |
|
||||||
|
| `Command::SUCCESS` | Код успешного завершения |
|
||||||
|
| `Command::FAILURE` | Код неудачного завершения |
|
||||||
|
|
||||||
|
#### Методы для взаимодействия с пользователем
|
||||||
|
|
||||||
|
```php
|
||||||
|
// Вывод текста
|
||||||
|
$this->info('Сообщение'); // Зеленый текст
|
||||||
|
$this->error('Ошибка'); // Красный текст
|
||||||
|
$this->warn('Внимание'); // Желтый текст
|
||||||
|
$this->line('Текст'); // Обычный текст
|
||||||
|
|
||||||
|
// Запрос подтверждения
|
||||||
|
if ($this->confirm('Продолжить?')) {
|
||||||
|
// пользователь ответил "да"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Выбор из списка
|
||||||
|
$choice = $this->choice('Выберите:', ['opt1', 'opt2', 'opt3'], 0);
|
||||||
|
|
||||||
|
// Ввод текста
|
||||||
|
$name = $this->ask('Введите имя:');
|
||||||
|
$password = $this->secret('Введите пароль:'); // Скрытый ввод
|
||||||
|
|
||||||
|
// Прогресс-бар
|
||||||
|
$bar = $this->output->createProgressBar(100);
|
||||||
|
$bar->start();
|
||||||
|
foreach ($items as $item) {
|
||||||
|
// обработка
|
||||||
|
$bar->advance();
|
||||||
|
}
|
||||||
|
$bar->finish();
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.2 Список команд
|
||||||
|
|
||||||
|
| Команда | Описание |
|
||||||
|
|---------|----------|
|
||||||
|
| `php artisan swagger:generate` | Генерация OpenAPI спецификации |
|
||||||
|
| `php artisan list` | Список всех команд |
|
||||||
|
| `php artisan list api` | Список команд содержащих "api" |
|
||||||
|
|
||||||
|
### 7.3 Пример использования
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Генерация документации
|
||||||
|
php artisan swagger:generate
|
||||||
|
|
||||||
|
# Очистка кэша конфигурации
|
||||||
|
php artisan config:clear
|
||||||
|
|
||||||
|
# Очистка кэша маршрутов
|
||||||
|
php artisan route:clear
|
||||||
|
|
||||||
|
# Просмотр списка команд
|
||||||
|
php artisan list
|
||||||
|
|
||||||
|
# Справка по команде
|
||||||
|
php artisan swagger:generate --help
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.4 Автоматическая регистрация
|
||||||
|
|
||||||
|
Laravel автоматически обнаруживает команды в директории `app/Console/Commands`.
|
||||||
|
Для ручной регистрации добавьте в `app/Console/Kernel.php`:
|
||||||
|
|
||||||
|
```php
|
||||||
|
// app/Console/Kernel.php
|
||||||
|
|
||||||
|
namespace App\Console;
|
||||||
|
|
||||||
|
use Illuminate\Console\Scheduling\Schedule;
|
||||||
|
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
||||||
|
|
||||||
|
class Kernel extends ConsoleKernel
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The Artisan commands provided by your application.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $commands = [
|
||||||
|
Commands\GenerateSwagger::class,
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define the application's command schedule.
|
||||||
|
*/
|
||||||
|
protected function schedule(Schedule $schedule): void
|
||||||
|
{
|
||||||
|
// $schedule->command('swagger:generate')->daily();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register the commands for the application.
|
||||||
|
*/
|
||||||
|
protected function commands(): void
|
||||||
|
{
|
||||||
|
$this->load(__DIR__.'/Commands');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Политики доступа
|
||||||
|
|
||||||
|
### 8.1 PostPolicy
|
||||||
|
|
||||||
|
```php
|
||||||
|
// app/Policies/PostPolicy.php
|
||||||
|
|
||||||
|
namespace App\Policies;
|
||||||
|
|
||||||
|
use App\Models\Post;
|
||||||
|
use App\Models\User;
|
||||||
|
|
||||||
|
class PostPolicy
|
||||||
|
{
|
||||||
|
public function update(User $user, Post $post): bool
|
||||||
|
{
|
||||||
|
return $user->id === $post->user_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete(User $user, Post $post): bool
|
||||||
|
{
|
||||||
|
return $user->id === $post->user_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.2 Регистрация политики
|
||||||
|
|
||||||
|
```php
|
||||||
|
// app/Providers/AuthServiceProvider.php
|
||||||
|
|
||||||
|
namespace App\Providers;
|
||||||
|
|
||||||
|
use App\Models\Post;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Policies\PostPolicy;
|
||||||
|
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
|
||||||
|
|
||||||
|
class AuthServiceProvider extends ServiceProvider
|
||||||
|
{
|
||||||
|
protected $policies = [
|
||||||
|
Post::class => PostPolicy::class,
|
||||||
|
];
|
||||||
|
|
||||||
|
public function boot(): void
|
||||||
|
{
|
||||||
|
$this->registerPolicies();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Конфигурация .env
|
||||||
|
|
||||||
|
Убедитесь, что в файле `.env` указаны правильные настройки:
|
||||||
|
|
||||||
|
```env
|
||||||
|
APP_NAME=Laravel
|
||||||
|
APP_ENV=local
|
||||||
|
APP_KEY=base64:...
|
||||||
|
APP_URL=http://la.test
|
||||||
|
|
||||||
|
DB_CONNECTION=sqlite
|
||||||
|
|
||||||
|
SESSION_DRIVER=database
|
||||||
|
SANCTUM_STATEFUL_DOMAINS=la.test,localhost,127.0.0.1
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Тестирование API
|
||||||
|
|
||||||
|
### 10.1 Примеры запросов в Swagger UI
|
||||||
|
|
||||||
|
1. Откройте `http://la.test/api/docs`
|
||||||
|
2. Нажмите **Authorize** и введите ваш token
|
||||||
|
3. Тестируйте endpoints прямо в браузере
|
||||||
|
|
||||||
|
### 10.2 Ручное тестирование
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Проверка списка маршрутов
|
||||||
|
php artisan route:list
|
||||||
|
|
||||||
|
# Запуск сервера
|
||||||
|
php artisan serve --host=la.test
|
||||||
|
|
||||||
|
# Запуск тестов
|
||||||
|
php artisan test
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Проблема: 401 Unauthorized
|
||||||
|
|
||||||
|
**Решение:**
|
||||||
|
1. Убедитесь, что передаёте правильный токен в заголовке `Authorization`
|
||||||
|
2. Проверьте срок действия токена
|
||||||
|
3. Убедитесь, что используете префикс `Bearer`
|
||||||
|
|
||||||
|
### Проблема: CSRF token mismatch
|
||||||
|
|
||||||
|
**Решение:**
|
||||||
|
Для SPA приложений добавьте домен в `SANCTUM_STATEFUL_DOMAINS`:
|
||||||
|
|
||||||
|
```env
|
||||||
|
SANCTUM_STATEFUL_DOMAINS=la.test,localhost,127.0.0.1
|
||||||
|
```
|
||||||
|
|
||||||
|
### Проблема: Таблица уже существует
|
||||||
|
|
||||||
|
**Решение:**
|
||||||
|
Если миграция `personal_access_tokens` уже выполнена, пропустите её:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
php artisan migrate --path=/database/migrations/2026_03_20_032532_create_personal_access_tokens_table.php --skip
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Заключение
|
||||||
|
|
||||||
|
Теперь у вас есть полностью настроенный API с:
|
||||||
|
- ✅ Laravel Sanctum для аутентификации
|
||||||
|
- ✅ Token-based авторизация
|
||||||
|
- ✅ API Resources для форматирования ответов
|
||||||
|
- ✅ Form Requests для валидации
|
||||||
|
- ✅ Swagger документация по адресу `/api/docs`
|
||||||
|
- ✅ Политики доступа для постов
|
||||||
|
|
||||||
|
Для получения дополнительной информации обратитесь к официальной документации:
|
||||||
|
- [Laravel Sanctum](https://laravel.com/docs/sanctum)
|
||||||
|
- [wadakatu/laravel-spectrum](https://github.com/wadakatu/laravel-spectrum)
|
||||||
|
|
@ -0,0 +1,252 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Support\Facades\File;
|
||||||
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
|
class GenerateSwagger extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'swagger:generate';
|
||||||
|
protected $description = 'Generate OpenAPI specification for the API';
|
||||||
|
|
||||||
|
public function handle(): int
|
||||||
|
{
|
||||||
|
$this->info('Generating OpenAPI specification...');
|
||||||
|
|
||||||
|
$outputDir = public_path('docs');
|
||||||
|
if (!File::exists($outputDir)) {
|
||||||
|
File::makeDirectory($outputDir, 0755, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$spec = $this->generateSpec();
|
||||||
|
$yaml = Yaml::dump($spec, 4, 2);
|
||||||
|
|
||||||
|
File::put($outputDir . '/openapi.yaml', $yaml);
|
||||||
|
$this->info('OpenAPI specification saved to public/docs/openapi.yaml');
|
||||||
|
|
||||||
|
return Command::SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function generateSpec(): array
|
||||||
|
{
|
||||||
|
$appUrl = config('app.url', 'http://localhost');
|
||||||
|
|
||||||
|
return [
|
||||||
|
'openapi' => '3.0.0',
|
||||||
|
'info' => [
|
||||||
|
'title' => config('app.name', 'Laravel API'),
|
||||||
|
'description' => 'API Documentation',
|
||||||
|
'version' => '1.0.0',
|
||||||
|
],
|
||||||
|
'servers' => [
|
||||||
|
['url' => $appUrl, 'description' => 'Local server'],
|
||||||
|
],
|
||||||
|
'paths' => $this->generatePaths(),
|
||||||
|
'components' => [
|
||||||
|
'securitySchemes' => [
|
||||||
|
'bearerAuth' => [
|
||||||
|
'type' => 'http',
|
||||||
|
'scheme' => 'bearer',
|
||||||
|
'bearerFormat' => 'JWT',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'schemas' => $this->generateSchemas(),
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function generatePaths(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'/api/register' => [
|
||||||
|
'post' => [
|
||||||
|
'tags' => ['Auth'],
|
||||||
|
'summary' => 'Register a new user',
|
||||||
|
'requestBody' => [
|
||||||
|
'required' => true,
|
||||||
|
'content' => [
|
||||||
|
'application/json' => [
|
||||||
|
'schema' => [
|
||||||
|
'type' => 'object',
|
||||||
|
'required' => ['name', 'email', 'password', 'password_confirmation'],
|
||||||
|
'properties' => [
|
||||||
|
'name' => ['type' => 'string', 'example' => 'John Doe'],
|
||||||
|
'email' => ['type' => 'string', 'format' => 'email', 'example' => 'john@example.com'],
|
||||||
|
'password' => ['type' => 'string', 'format' => 'password', 'example' => 'password123'],
|
||||||
|
'password_confirmation' => ['type' => 'string', 'format' => 'password', 'example' => 'password123'],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'responses' => [
|
||||||
|
'201' => ['description' => 'User registered successfully'],
|
||||||
|
'422' => ['description' => 'Validation error'],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'/api/login' => [
|
||||||
|
'post' => [
|
||||||
|
'tags' => ['Auth'],
|
||||||
|
'summary' => 'Login user',
|
||||||
|
'requestBody' => [
|
||||||
|
'required' => true,
|
||||||
|
'content' => [
|
||||||
|
'application/json' => [
|
||||||
|
'schema' => [
|
||||||
|
'type' => 'object',
|
||||||
|
'required' => ['email', 'password'],
|
||||||
|
'properties' => [
|
||||||
|
'email' => ['type' => 'string', 'format' => 'email', 'example' => 'john@example.com'],
|
||||||
|
'password' => ['type' => 'string', 'format' => 'password', 'example' => 'password123'],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'responses' => [
|
||||||
|
'200' => ['description' => 'Login successful'],
|
||||||
|
'401' => ['description' => 'Invalid credentials'],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'/api/logout' => [
|
||||||
|
'post' => [
|
||||||
|
'tags' => ['Auth'],
|
||||||
|
'summary' => 'Logout user',
|
||||||
|
'security' => [['bearerAuth' => []]],
|
||||||
|
'responses' => [
|
||||||
|
'200' => ['description' => 'Logged out successfully'],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'/api/user' => [
|
||||||
|
'get' => [
|
||||||
|
'tags' => ['Auth'],
|
||||||
|
'summary' => 'Get authenticated user',
|
||||||
|
'security' => [['bearerAuth' => []]],
|
||||||
|
'responses' => [
|
||||||
|
'200' => ['description' => 'User data'],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'/api/posts' => [
|
||||||
|
'get' => [
|
||||||
|
'tags' => ['Posts'],
|
||||||
|
'summary' => 'Get all posts',
|
||||||
|
'security' => [['bearerAuth' => []]],
|
||||||
|
'responses' => [
|
||||||
|
'200' => ['description' => 'List of posts'],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'post' => [
|
||||||
|
'tags' => ['Posts'],
|
||||||
|
'summary' => 'Create a new post',
|
||||||
|
'security' => [['bearerAuth' => []]],
|
||||||
|
'requestBody' => [
|
||||||
|
'required' => true,
|
||||||
|
'content' => [
|
||||||
|
'application/json' => [
|
||||||
|
'schema' => [
|
||||||
|
'type' => 'object',
|
||||||
|
'required' => ['title', 'content'],
|
||||||
|
'properties' => [
|
||||||
|
'title' => ['type' => 'string', 'example' => 'My Post Title'],
|
||||||
|
'content' => ['type' => 'string', 'example' => 'Post content here...'],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'responses' => [
|
||||||
|
'201' => ['description' => 'Post created successfully'],
|
||||||
|
'422' => ['description' => 'Validation error'],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'/api/posts/{id}' => [
|
||||||
|
'get' => [
|
||||||
|
'tags' => ['Posts'],
|
||||||
|
'summary' => 'Get a post',
|
||||||
|
'security' => [['bearerAuth' => []]],
|
||||||
|
'parameters' => [
|
||||||
|
['name' => 'id', 'in' => 'path', 'required' => true, 'schema' => ['type' => 'integer']],
|
||||||
|
],
|
||||||
|
'responses' => [
|
||||||
|
'200' => ['description' => 'Post data'],
|
||||||
|
'404' => ['description' => 'Post not found'],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'put' => [
|
||||||
|
'tags' => ['Posts'],
|
||||||
|
'summary' => 'Update a post',
|
||||||
|
'security' => [['bearerAuth' => []]],
|
||||||
|
'parameters' => [
|
||||||
|
['name' => 'id', 'in' => 'path', 'required' => true, 'schema' => ['type' => 'integer']],
|
||||||
|
],
|
||||||
|
'requestBody' => [
|
||||||
|
'required' => true,
|
||||||
|
'content' => [
|
||||||
|
'application/json' => [
|
||||||
|
'schema' => [
|
||||||
|
'type' => 'object',
|
||||||
|
'properties' => [
|
||||||
|
'title' => ['type' => 'string', 'example' => 'Updated Title'],
|
||||||
|
'content' => ['type' => 'string', 'example' => 'Updated content...'],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'responses' => [
|
||||||
|
'200' => ['description' => 'Post updated successfully'],
|
||||||
|
'404' => ['description' => 'Post not found'],
|
||||||
|
'422' => ['description' => 'Validation error'],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'delete' => [
|
||||||
|
'tags' => ['Posts'],
|
||||||
|
'summary' => 'Delete a post',
|
||||||
|
'security' => [['bearerAuth' => []]],
|
||||||
|
'parameters' => [
|
||||||
|
['name' => 'id', 'in' => 'path', 'required' => true, 'schema' => ['type' => 'integer']],
|
||||||
|
],
|
||||||
|
'responses' => [
|
||||||
|
'200' => ['description' => 'Post deleted successfully'],
|
||||||
|
'404' => ['description' => 'Post not found'],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function generateSchemas(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'User' => [
|
||||||
|
'type' => 'object',
|
||||||
|
'properties' => [
|
||||||
|
'id' => ['type' => 'integer'],
|
||||||
|
'name' => ['type' => 'string'],
|
||||||
|
'email' => ['type' => 'string', 'format' => 'email'],
|
||||||
|
'role' => ['type' => 'string'],
|
||||||
|
'created_at' => ['type' => 'string', 'format' => 'date-time'],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'Post' => [
|
||||||
|
'type' => 'object',
|
||||||
|
'properties' => [
|
||||||
|
'id' => ['type' => 'integer'],
|
||||||
|
'title' => ['type' => 'string'],
|
||||||
|
'content' => ['type' => 'string'],
|
||||||
|
'user_id' => ['type' => 'integer'],
|
||||||
|
'user' => ['$ref' => '#/components/schemas/User'],
|
||||||
|
'created_at' => ['type' => 'string', 'format' => 'date-time'],
|
||||||
|
'updated_at' => ['type' => 'string', 'format' => 'date-time'],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,80 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Http\Requests\LoginRequest;
|
||||||
|
use App\Http\Requests\RegisterRequest;
|
||||||
|
use App\Http\Resources\UserResource;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
|
||||||
|
class AuthController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Register a new user.
|
||||||
|
*/
|
||||||
|
public function register(RegisterRequest $request): JsonResponse
|
||||||
|
{
|
||||||
|
$user = User::create([
|
||||||
|
'name' => $request->name,
|
||||||
|
'email' => $request->email,
|
||||||
|
'password' => Hash::make($request->password),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$token = $user->createToken('auth-token')->plainTextToken;
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'User registered successfully',
|
||||||
|
'user' => new UserResource($user),
|
||||||
|
'token' => $token,
|
||||||
|
], 201);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Login user.
|
||||||
|
*/
|
||||||
|
public function login(LoginRequest $request): JsonResponse
|
||||||
|
{
|
||||||
|
$user = User::where('email', $request->email)->first();
|
||||||
|
|
||||||
|
if (!$user || !Hash::check($request->password, $user->password)) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Invalid credentials',
|
||||||
|
], 401);
|
||||||
|
}
|
||||||
|
|
||||||
|
$token = $user->createToken('auth-token')->plainTextToken;
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Login successful',
|
||||||
|
'user' => new UserResource($user),
|
||||||
|
'token' => $token,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logout user.
|
||||||
|
*/
|
||||||
|
public function logout(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$request->user()->currentAccessToken()->delete();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Logged out successfully',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get authenticated user.
|
||||||
|
*/
|
||||||
|
public function user(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
return response()->json([
|
||||||
|
'user' => new UserResource($request->user()),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,83 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Http\Requests\StorePostRequest;
|
||||||
|
use App\Http\Requests\UpdatePostRequest;
|
||||||
|
use App\Http\Resources\PostCollection;
|
||||||
|
use App\Http\Resources\PostResource;
|
||||||
|
use App\Models\Post;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class PostController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Display a listing of posts.
|
||||||
|
*/
|
||||||
|
public function index(): PostCollection
|
||||||
|
{
|
||||||
|
$posts = Post::with('user')->latest()->paginate(15);
|
||||||
|
return new PostCollection($posts);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store a newly created post.
|
||||||
|
*/
|
||||||
|
public function store(StorePostRequest $request): JsonResponse
|
||||||
|
{
|
||||||
|
$post = Post::create([
|
||||||
|
'title' => $request->title,
|
||||||
|
'content' => $request->content,
|
||||||
|
'user_id' => $request->user()->id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$post->load('user');
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Post created successfully',
|
||||||
|
'post' => new PostResource($post),
|
||||||
|
], 201);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display the specified post.
|
||||||
|
*/
|
||||||
|
public function show(Post $post): JsonResponse
|
||||||
|
{
|
||||||
|
$post->load('user');
|
||||||
|
return response()->json([
|
||||||
|
'post' => new PostResource($post),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the specified post.
|
||||||
|
*/
|
||||||
|
public function update(UpdatePostRequest $request, Post $post): JsonResponse
|
||||||
|
{
|
||||||
|
$this->authorize('update', $post);
|
||||||
|
|
||||||
|
$post->update($request->validated());
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Post updated successfully',
|
||||||
|
'post' => new PostResource($post),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the specified post.
|
||||||
|
*/
|
||||||
|
public function destroy(Post $post): JsonResponse
|
||||||
|
{
|
||||||
|
$this->authorize('delete', $post);
|
||||||
|
|
||||||
|
$post->delete();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Post deleted successfully',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class LoginRequest 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 [
|
||||||
|
'email' => ['required', 'string', 'email'],
|
||||||
|
'password' => ['required', 'string'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class RegisterRequest 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 [
|
||||||
|
'name' => ['required', 'string', 'max:255'],
|
||||||
|
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
|
||||||
|
'password' => ['required', 'string', 'min:8', 'confirmed'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class StorePostRequest 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:255'],
|
||||||
|
'content' => ['required', 'string'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class UpdatePostRequest 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', 'string', 'max:255'],
|
||||||
|
'content' => ['sometimes', 'string'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Resources;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Http\Resources\Json\ResourceCollection;
|
||||||
|
|
||||||
|
class PostCollection extends ResourceCollection
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The resource that this resource collects.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public $collects = PostResource::class;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform the resource collection into an array.
|
||||||
|
*
|
||||||
|
* @return array<int|string, mixed>
|
||||||
|
*/
|
||||||
|
public function toArray(Request $request): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'data' => $this->collection,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Resources;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
|
||||||
|
class PostResource 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,
|
||||||
|
'content' => $this->content,
|
||||||
|
'user_id' => $this->user_id,
|
||||||
|
'user' => new UserResource($this->whenLoaded('user')),
|
||||||
|
'created_at' => $this->created_at,
|
||||||
|
'updated_at' => $this->updated_at,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Resources;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
|
||||||
|
class UserResource 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,
|
||||||
|
'email' => $this->email,
|
||||||
|
'role' => $this->role,
|
||||||
|
'created_at' => $this->created_at,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,15 +2,14 @@
|
||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||||
use Illuminate\Notifications\Notifiable;
|
use Illuminate\Notifications\Notifiable;
|
||||||
|
use Laravel\Sanctum\HasApiTokens;
|
||||||
|
|
||||||
class User extends Authenticatable
|
class User extends Authenticatable
|
||||||
{
|
{
|
||||||
/** @use HasFactory<\Database\Factories\UserFactory> */
|
use HasApiTokens, HasFactory, Notifiable;
|
||||||
use HasFactory, Notifiable;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The attributes that are mass assignable.
|
* The attributes that are mass assignable.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Policies;
|
||||||
|
|
||||||
|
use App\Models\Post;
|
||||||
|
use App\Models\User;
|
||||||
|
|
||||||
|
class PostPolicy
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine whether the user can update the post.
|
||||||
|
*/
|
||||||
|
public function update(User $user, Post $post): bool
|
||||||
|
{
|
||||||
|
return $user->id === $post->user_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether the user can delete the post.
|
||||||
|
*/
|
||||||
|
public function delete(User $user, Post $post): bool
|
||||||
|
{
|
||||||
|
return $user->id === $post->user_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,6 +7,7 @@ use Illuminate\Foundation\Configuration\Middleware;
|
||||||
return Application::configure(basePath: dirname(__DIR__))
|
return Application::configure(basePath: dirname(__DIR__))
|
||||||
->withRouting(
|
->withRouting(
|
||||||
web: __DIR__.'/../routes/web.php',
|
web: __DIR__.'/../routes/web.php',
|
||||||
|
api: __DIR__.'/../routes/api.php',
|
||||||
commands: __DIR__.'/../routes/console.php',
|
commands: __DIR__.'/../routes/console.php',
|
||||||
health: '/up',
|
health: '/up',
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,10 @@
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^8.2",
|
"php": "^8.2",
|
||||||
"laravel/framework": "^12.0",
|
"laravel/framework": "^12.0",
|
||||||
|
"laravel/sanctum": "^4.3",
|
||||||
"laravel/tinker": "^2.10.1",
|
"laravel/tinker": "^2.10.1",
|
||||||
"laravel/ui": "^4.6"
|
"laravel/ui": "^4.6",
|
||||||
|
"wadakatu/laravel-spectrum": "^1.1"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"fakerphp/faker": "^1.23",
|
"fakerphp/faker": "^1.23",
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "4776810dc1f9cc2ee65b6526f0091af8",
|
"content-hash": "71b27411bdfa32f8d8a868f991c3dccb",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "brick/math",
|
"name": "brick/math",
|
||||||
|
|
@ -1333,6 +1333,69 @@
|
||||||
},
|
},
|
||||||
"time": "2026-02-06T12:17:10+00:00"
|
"time": "2026-02-06T12:17:10+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "laravel/sanctum",
|
||||||
|
"version": "v4.3.1",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/laravel/sanctum.git",
|
||||||
|
"reference": "e3b85d6e36ad00e5db2d1dcc27c81ffdf15cbf76"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/laravel/sanctum/zipball/e3b85d6e36ad00e5db2d1dcc27c81ffdf15cbf76",
|
||||||
|
"reference": "e3b85d6e36ad00e5db2d1dcc27c81ffdf15cbf76",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-json": "*",
|
||||||
|
"illuminate/console": "^11.0|^12.0|^13.0",
|
||||||
|
"illuminate/contracts": "^11.0|^12.0|^13.0",
|
||||||
|
"illuminate/database": "^11.0|^12.0|^13.0",
|
||||||
|
"illuminate/support": "^11.0|^12.0|^13.0",
|
||||||
|
"php": "^8.2",
|
||||||
|
"symfony/console": "^7.0|^8.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"mockery/mockery": "^1.6",
|
||||||
|
"orchestra/testbench": "^9.15|^10.8|^11.0",
|
||||||
|
"phpstan/phpstan": "^1.10"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"laravel": {
|
||||||
|
"providers": [
|
||||||
|
"Laravel\\Sanctum\\SanctumServiceProvider"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Laravel\\Sanctum\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Taylor Otwell",
|
||||||
|
"email": "taylor@laravel.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Laravel Sanctum provides a featherweight authentication system for SPAs and simple APIs.",
|
||||||
|
"keywords": [
|
||||||
|
"auth",
|
||||||
|
"laravel",
|
||||||
|
"sanctum"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/laravel/sanctum/issues",
|
||||||
|
"source": "https://github.com/laravel/sanctum"
|
||||||
|
},
|
||||||
|
"time": "2026-02-07T17:19:31+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "laravel/serializable-closure",
|
"name": "laravel/serializable-closure",
|
||||||
"version": "v2.0.10",
|
"version": "v2.0.10",
|
||||||
|
|
@ -3357,6 +3420,71 @@
|
||||||
},
|
},
|
||||||
"time": "2025-12-14T04:43:48+00:00"
|
"time": "2025-12-14T04:43:48+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "spatie/fork",
|
||||||
|
"version": "1.2.5",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/spatie/fork.git",
|
||||||
|
"reference": "bde768a99be8cff41b8ec4991b016dcb58f414e8"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/spatie/fork/zipball/bde768a99be8cff41b8ec4991b016dcb58f414e8",
|
||||||
|
"reference": "bde768a99be8cff41b8ec4991b016dcb58f414e8",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-pcntl": "*",
|
||||||
|
"ext-sockets": "*",
|
||||||
|
"php": "^8.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"nesbot/carbon": "^2.66",
|
||||||
|
"pestphp/pest": "^1.23",
|
||||||
|
"phpunit/phpunit": "^9.5",
|
||||||
|
"spatie/ray": "^1.10"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Spatie\\Fork\\": "src"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Brent Roose",
|
||||||
|
"email": "brent@spatie.be",
|
||||||
|
"role": "Developer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Freek Van der Herten",
|
||||||
|
"email": "freek@spatie.be",
|
||||||
|
"role": "Developer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "A lightweight solution for running code concurrently in PHP",
|
||||||
|
"homepage": "https://github.com/spatie/fork",
|
||||||
|
"keywords": [
|
||||||
|
"fork",
|
||||||
|
"spatie"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/spatie/fork/issues",
|
||||||
|
"source": "https://github.com/spatie/fork/tree/1.2.5"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://github.com/spatie",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2025-04-24T08:58:04+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/clock",
|
"name": "symfony/clock",
|
||||||
"version": "v8.0.0",
|
"version": "v8.0.0",
|
||||||
|
|
@ -6061,6 +6189,204 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2024-11-21T01:49:47+00:00"
|
"time": "2024-11-21T01:49:47+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "wadakatu/laravel-spectrum",
|
||||||
|
"version": "v1.1.3",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/wadakatu/laravel-spectrum.git",
|
||||||
|
"reference": "0c80efc8456a7dcb910f62e37f15cee95b4d0683"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/wadakatu/laravel-spectrum/zipball/0c80efc8456a7dcb910f62e37f15cee95b4d0683",
|
||||||
|
"reference": "0c80efc8456a7dcb910f62e37f15cee95b4d0683",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"illuminate/console": "^11.0|^12.0",
|
||||||
|
"illuminate/routing": "^11.0|^12.0",
|
||||||
|
"illuminate/support": "^11.0|^12.0",
|
||||||
|
"nikic/php-parser": "^5.5",
|
||||||
|
"php": "^8.2",
|
||||||
|
"spatie/fork": "^1.2",
|
||||||
|
"symfony/finder": "^6.0|^7.0",
|
||||||
|
"workerman/workerman": "^5.1"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"devizzent/cebe-php-openapi": "^1.1",
|
||||||
|
"fakerphp/faker": "^1.23",
|
||||||
|
"infection/infection": "*",
|
||||||
|
"laravel/pint": "^1.23",
|
||||||
|
"orchestra/testbench": "^9.0|^10.0",
|
||||||
|
"phpstan/phpstan": "^2.1",
|
||||||
|
"phpunit/phpunit": "^10.0|^11.0|^12.0",
|
||||||
|
"spatie/phpunit-snapshot-assertions": "^5.2"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"laravel": {
|
||||||
|
"providers": [
|
||||||
|
"LaravelSpectrum\\SpectrumServiceProvider"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"LaravelSpectrum\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "wadakatu",
|
||||||
|
"email": "wadakatukoyo330@gmail.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Zero-annotation OpenAPI/Swagger documentation generator for Laravel - automatic API docs from your existing code",
|
||||||
|
"homepage": "https://github.com/wadakatu/laravel-spectrum",
|
||||||
|
"keywords": [
|
||||||
|
"api",
|
||||||
|
"api-documentation",
|
||||||
|
"automatic",
|
||||||
|
"documentation",
|
||||||
|
"generator",
|
||||||
|
"laravel",
|
||||||
|
"mock-server",
|
||||||
|
"no-annotation",
|
||||||
|
"openapi",
|
||||||
|
"rest-api",
|
||||||
|
"swagger",
|
||||||
|
"swagger-alternative",
|
||||||
|
"zero-annotation"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"changelog": "https://github.com/wadakatu/laravel-spectrum/releases",
|
||||||
|
"docs": "https://github.com/wadakatu/laravel-spectrum#readme",
|
||||||
|
"issues": "https://github.com/wadakatu/laravel-spectrum/issues",
|
||||||
|
"source": "https://github.com/wadakatu/laravel-spectrum"
|
||||||
|
},
|
||||||
|
"time": "2026-02-16T10:52:56+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "workerman/coroutine",
|
||||||
|
"version": "v1.1.5",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/workerman-php/coroutine.git",
|
||||||
|
"reference": "b60e44267b90d398dbfa7a320f3e97b46357ac9f"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/workerman-php/coroutine/zipball/b60e44267b90d398dbfa7a320f3e97b46357ac9f",
|
||||||
|
"reference": "b60e44267b90d398dbfa7a320f3e97b46357ac9f",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=8.1",
|
||||||
|
"workerman/workerman": "^5.1"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "^11.0",
|
||||||
|
"psr/log": "*"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Workerman\\": "src",
|
||||||
|
"Workerman\\Coroutine\\": "src"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"description": "Workerman coroutine",
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/workerman-php/coroutine/issues",
|
||||||
|
"source": "https://github.com/workerman-php/coroutine/tree/v1.1.5"
|
||||||
|
},
|
||||||
|
"time": "2026-03-12T02:07:37+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "workerman/workerman",
|
||||||
|
"version": "v5.1.9",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/walkor/workerman.git",
|
||||||
|
"reference": "fff0954628f8ceeccfe29d3e817f0fad87cfdbf2"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/walkor/workerman/zipball/fff0954628f8ceeccfe29d3e817f0fad87cfdbf2",
|
||||||
|
"reference": "fff0954628f8ceeccfe29d3e817f0fad87cfdbf2",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-json": "*",
|
||||||
|
"php": ">=8.1",
|
||||||
|
"workerman/coroutine": "^1.1 || dev-main"
|
||||||
|
},
|
||||||
|
"conflict": {
|
||||||
|
"ext-swow": "<v1.0.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"guzzlehttp/guzzle": "^7.0",
|
||||||
|
"mockery/mockery": "^1.6",
|
||||||
|
"pestphp/pest": "^2.36 || ^3 || ^4",
|
||||||
|
"phpstan/phpstan": "2.1.x-dev"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"ext-event": "For better performance. "
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Workerman\\": "src"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "walkor",
|
||||||
|
"email": "walkor@workerman.net",
|
||||||
|
"homepage": "https://www.workerman.net",
|
||||||
|
"role": "Developer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "An asynchronous event driven PHP framework for easily building fast, scalable network applications.",
|
||||||
|
"homepage": "https://www.workerman.net",
|
||||||
|
"keywords": [
|
||||||
|
"asynchronous",
|
||||||
|
"event-loop",
|
||||||
|
"framework",
|
||||||
|
"http"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"email": "walkor@workerman.net",
|
||||||
|
"forum": "https://www.workerman.net/questions",
|
||||||
|
"issues": "https://github.com/walkor/workerman/issues",
|
||||||
|
"source": "https://github.com/walkor/workerman",
|
||||||
|
"wiki": "https://www.workerman.net/doc/workerman/"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://opencollective.com/workerman",
|
||||||
|
"type": "open_collective"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://www.patreon.com/walkor",
|
||||||
|
"type": "patreon"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2026-01-09T03:26:15+00:00"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"packages-dev": [
|
"packages-dev": [
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Laravel\Sanctum\Sanctum;
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Stateful Domains
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Requests from the following domains / hosts will receive stateful API
|
||||||
|
| authentication cookies. Typically, these should include your local
|
||||||
|
| and production domains which access your API via a frontend SPA.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
|
||||||
|
'%s%s',
|
||||||
|
'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1',
|
||||||
|
Sanctum::currentApplicationUrlWithPort(),
|
||||||
|
// Sanctum::currentRequestHost(),
|
||||||
|
))),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Sanctum Guards
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This array contains the authentication guards that will be checked when
|
||||||
|
| Sanctum is trying to authenticate a request. If none of these guards
|
||||||
|
| are able to authenticate the request, Sanctum will use the bearer
|
||||||
|
| token that's present on an incoming request for authentication.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'guard' => ['web'],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Expiration Minutes
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This value controls the number of minutes until an issued token will be
|
||||||
|
| considered expired. This will override any values set in the token's
|
||||||
|
| "expires_at" attribute, but first-party sessions are not affected.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'expiration' => null,
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Token Prefix
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Sanctum can prefix new tokens in order to take advantage of numerous
|
||||||
|
| security scanning initiatives maintained by open source platforms
|
||||||
|
| that notify developers if they commit tokens into repositories.
|
||||||
|
|
|
||||||
|
| See: https://docs.github.com/en/code-security/secret-scanning/about-secret-scanning
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'token_prefix' => env('SANCTUM_TOKEN_PREFIX', ''),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Sanctum Middleware
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| When authenticating your first-party SPA with Sanctum you may need to
|
||||||
|
| customize some of the middleware Sanctum uses while processing the
|
||||||
|
| request. You may change the middleware listed below as required.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'middleware' => [
|
||||||
|
'authenticate_session' => Laravel\Sanctum\Http\Middleware\AuthenticateSession::class,
|
||||||
|
'encrypt_cookies' => Illuminate\Cookie\Middleware\EncryptCookies::class,
|
||||||
|
'validate_csrf_token' => Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class,
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Spectrum Configuration
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This file contains the configuration for the Laravel Spectrum package.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Output directory for generated OpenAPI specification
|
||||||
|
'output' => 'public/docs',
|
||||||
|
|
||||||
|
// Base API documentation information
|
||||||
|
'title' => env('APP_NAME', 'Laravel API'),
|
||||||
|
'description' => 'API Documentation',
|
||||||
|
'version' => '1.0.0',
|
||||||
|
|
||||||
|
// Server URL (uses APP_URL from .env)
|
||||||
|
'server_url' => env('APP_URL'),
|
||||||
|
|
||||||
|
// Additional servers for documentation
|
||||||
|
'servers' => [
|
||||||
|
[
|
||||||
|
'url' => env('APP_URL'),
|
||||||
|
'description' => 'Local server',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
// Security schemes
|
||||||
|
'security_schemes' => [
|
||||||
|
'bearerAuth' => [
|
||||||
|
'type' => 'http',
|
||||||
|
'scheme' => 'bearer',
|
||||||
|
'bearerFormat' => 'JWT',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
// Default security scheme to apply
|
||||||
|
'default_security_scheme' => 'bearerAuth',
|
||||||
|
|
||||||
|
// Paths to scan for OpenAPI attributes
|
||||||
|
'paths' => [
|
||||||
|
'controllers' => [
|
||||||
|
'App\\Http\\Controllers\\Api\\',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
<?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('personal_access_tokens', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->morphs('tokenable');
|
||||||
|
$table->text('name');
|
||||||
|
$table->string('token', 64)->unique();
|
||||||
|
$table->text('abilities')->nullable();
|
||||||
|
$table->timestamp('last_used_at')->nullable();
|
||||||
|
$table->timestamp('expires_at')->nullable()->index();
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('personal_access_tokens');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>API Documentation - Swagger UI</title>
|
||||||
|
<link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@5.9.0/swagger-ui.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="swagger-ui"></div>
|
||||||
|
<script src="https://unpkg.com/swagger-ui-dist@5.9.0/swagger-ui-bundle.js"></script>
|
||||||
|
<script>
|
||||||
|
window.onload = function() {
|
||||||
|
SwaggerUIBundle({
|
||||||
|
url: "/docs/openapi.yaml",
|
||||||
|
dom_id: '#swagger-ui',
|
||||||
|
presets: [
|
||||||
|
SwaggerUIBundle.presets.apis
|
||||||
|
],
|
||||||
|
docExpansion: "list",
|
||||||
|
persistAuthorization: true
|
||||||
|
});
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
openapi: 3.0.0
|
||||||
|
info:
|
||||||
|
title: Laravel
|
||||||
|
description: 'API Documentation'
|
||||||
|
version: 1.0.0
|
||||||
|
servers:
|
||||||
|
-
|
||||||
|
url: 'http://la.test'
|
||||||
|
description: 'Local server'
|
||||||
|
paths:
|
||||||
|
/api/register:
|
||||||
|
post:
|
||||||
|
tags: [Auth]
|
||||||
|
summary: 'Register a new user'
|
||||||
|
requestBody: { required: true, content: { application/json: { schema: { type: object, required: [name, email, password, password_confirmation], properties: { name: { type: string, example: 'John Doe' }, email: { type: string, format: email, example: john@example.com }, password: { type: string, format: password, example: password123 }, password_confirmation: { type: string, format: password, example: password123 } } } } } }
|
||||||
|
responses: { 201: { description: 'User registered successfully' }, 422: { description: 'Validation error' } }
|
||||||
|
/api/login:
|
||||||
|
post:
|
||||||
|
tags: [Auth]
|
||||||
|
summary: 'Login user'
|
||||||
|
requestBody: { required: true, content: { application/json: { schema: { type: object, required: [email, password], properties: { email: { type: string, format: email, example: john@example.com }, password: { type: string, format: password, example: password123 } } } } } }
|
||||||
|
responses: { 200: { description: 'Login successful' }, 401: { description: 'Invalid credentials' } }
|
||||||
|
/api/logout:
|
||||||
|
post:
|
||||||
|
tags: [Auth]
|
||||||
|
summary: 'Logout user'
|
||||||
|
security: [{ bearerAuth: { } }]
|
||||||
|
responses: { 200: { description: 'Logged out successfully' } }
|
||||||
|
/api/user:
|
||||||
|
get:
|
||||||
|
tags: [Auth]
|
||||||
|
summary: 'Get authenticated user'
|
||||||
|
security: [{ bearerAuth: { } }]
|
||||||
|
responses: { 200: { description: 'User data' } }
|
||||||
|
/api/posts:
|
||||||
|
get:
|
||||||
|
tags: [Posts]
|
||||||
|
summary: 'Get all posts'
|
||||||
|
security: [{ bearerAuth: { } }]
|
||||||
|
responses: { 200: { description: 'List of posts' } }
|
||||||
|
post:
|
||||||
|
tags: [Posts]
|
||||||
|
summary: 'Create a new post'
|
||||||
|
security: [{ bearerAuth: { } }]
|
||||||
|
requestBody: { required: true, content: { application/json: { schema: { type: object, required: [title, content], properties: { title: { type: string, example: 'My Post Title' }, content: { type: string, example: 'Post content here...' } } } } } }
|
||||||
|
responses: { 201: { description: 'Post created successfully' }, 422: { description: 'Validation error' } }
|
||||||
|
'/api/posts/{id}':
|
||||||
|
get:
|
||||||
|
tags: [Posts]
|
||||||
|
summary: 'Get a post'
|
||||||
|
security: [{ bearerAuth: { } }]
|
||||||
|
parameters: [{ name: id, in: path, required: true, schema: { type: integer } }]
|
||||||
|
responses: { 200: { description: 'Post data' }, 404: { description: 'Post not found' } }
|
||||||
|
put:
|
||||||
|
tags: [Posts]
|
||||||
|
summary: 'Update a post'
|
||||||
|
security: [{ bearerAuth: { } }]
|
||||||
|
parameters: [{ name: id, in: path, required: true, schema: { type: integer } }]
|
||||||
|
requestBody: { required: true, content: { application/json: { schema: { type: object, properties: { title: { type: string, example: 'Updated Title' }, content: { type: string, example: 'Updated content...' } } } } } }
|
||||||
|
responses: { 200: { description: 'Post updated successfully' }, 404: { description: 'Post not found' }, 422: { description: 'Validation error' } }
|
||||||
|
delete:
|
||||||
|
tags: [Posts]
|
||||||
|
summary: 'Delete a post'
|
||||||
|
security: [{ bearerAuth: { } }]
|
||||||
|
parameters: [{ name: id, in: path, required: true, schema: { type: integer } }]
|
||||||
|
responses: { 200: { description: 'Post deleted successfully' }, 404: { description: 'Post not found' } }
|
||||||
|
components:
|
||||||
|
securitySchemes:
|
||||||
|
bearerAuth:
|
||||||
|
type: http
|
||||||
|
scheme: bearer
|
||||||
|
bearerFormat: JWT
|
||||||
|
schemas:
|
||||||
|
User:
|
||||||
|
type: object
|
||||||
|
properties: { id: { type: integer }, name: { type: string }, email: { type: string, format: email }, role: { type: string }, created_at: { type: string, format: date-time } }
|
||||||
|
Post:
|
||||||
|
type: object
|
||||||
|
properties: { id: { type: integer }, title: { type: string }, content: { type: string }, user_id: { type: integer }, user: { $ref: '#/components/schemas/User' }, created_at: { type: string, format: date-time }, updated_at: { type: string, format: date-time } }
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Http\Controllers\Api\AuthController;
|
||||||
|
use App\Http\Controllers\Api\PostController;
|
||||||
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| API Routes
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here is where you can register API routes for your application. These
|
||||||
|
| routes are loaded by the RouteServiceProvider and all of them will
|
||||||
|
| be assigned to the "api" middleware group.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Swagger Documentation
|
||||||
|
Route::get('/docs', function () {
|
||||||
|
return redirect()->to('/docs/index.html');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Public routes
|
||||||
|
Route::post('/register', [AuthController::class, 'register'])->name('api.register');
|
||||||
|
Route::post('/login', [AuthController::class, 'login'])->name('api.login');
|
||||||
|
|
||||||
|
// Protected routes
|
||||||
|
Route::middleware('auth:sanctum')->group(function () {
|
||||||
|
// Auth
|
||||||
|
Route::post('/logout', [AuthController::class, 'logout'])->name('api.logout');
|
||||||
|
Route::get('/user', [AuthController::class, 'user'])->name('api.user');
|
||||||
|
|
||||||
|
// Posts
|
||||||
|
Route::get('/posts', [PostController::class, 'index'])->name('api.posts.index');
|
||||||
|
Route::post('/posts', [PostController::class, 'store'])->name('api.posts.store');
|
||||||
|
Route::get('/posts/{post}', [PostController::class, 'show'])->name('api.posts.show');
|
||||||
|
Route::put('/posts/{post}', [PostController::class, 'update'])->name('api.posts.update');
|
||||||
|
Route::delete('/posts/{post}', [PostController::class, 'destroy'])->name('api.posts.destroy');
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue