Compare commits
10 Commits
4ebf4ec35f
...
e1efce8d59
| Author | SHA1 | Date | |
|---|---|---|---|
| e1efce8d59 | |||
| 7fdb0399ad | |||
| e327148cb1 | |||
| 2a43a8f8e0 | |||
| b176ff53f6 | |||
| 0cbf5f3dc9 | |||
| 3688645458 | |||
| 8d717426c8 | |||
| d8d49f7bba | |||
| ac47c7114d |
@@ -0,0 +1,55 @@
|
||||
APP_NAME=Laravel
|
||||
APP_ENV=local
|
||||
APP_KEY=
|
||||
APP_DEBUG=true
|
||||
APP_TIMEZONE=UTC
|
||||
APP_URL=http://localhost:8080
|
||||
|
||||
APP_LOCALE=en
|
||||
APP_FALLBACK_LOCALE=en
|
||||
APP_FAKER_LOCALE=en_US
|
||||
|
||||
APP_MAINTENANCE_DRIVER=file
|
||||
|
||||
BCRYPT_ROUNDS=12
|
||||
|
||||
LOG_CHANNEL=stack
|
||||
LOG_STACK=single
|
||||
LOG_LEVEL=debug
|
||||
|
||||
# Настройки подключения к MySQL в Docker
|
||||
DB_CONNECTION=mysql
|
||||
DB_HOST=db
|
||||
DB_PORT=3306
|
||||
DB_DATABASE=gigs_api
|
||||
DB_USERNAME=gigs_api
|
||||
DB_PASSWORD=gigs_api
|
||||
|
||||
SESSION_DRIVER=database
|
||||
SESSION_LIFETIME=120
|
||||
SESSION_ENCRYPT=false
|
||||
SESSION_PATH=/
|
||||
SESSION_DOMAIN=null
|
||||
|
||||
BROADCAST_CONNECTION=log
|
||||
FILESYSTEM_DISK=local
|
||||
QUEUE_CONNECTION=database
|
||||
|
||||
CACHE_STORE=database
|
||||
CACHE_PREFIX=
|
||||
|
||||
MEMCACHED_HOST=127.0.0.1
|
||||
|
||||
REDIS_CLIENT=phpredis
|
||||
REDIS_HOST=127.0.0.1
|
||||
REDIS_PASSWORD=null
|
||||
REDIS_PORT=6379
|
||||
|
||||
MAIL_MAILER=log
|
||||
MAIL_SCHEME=null
|
||||
MAIL_HOST=127.0.0.1
|
||||
MAIL_PORT=2525
|
||||
MAIL_USERNAME=null
|
||||
MAIL_PASSWORD=null
|
||||
MAIL_FROM_ADDRESS="hello@example.com"
|
||||
MAIL_FROM_NAME="${APP_NAME}"
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
# Секретные файлы конфигурации (их нельзя пушить!)
|
||||
.env
|
||||
.env.backup
|
||||
.env.production
|
||||
|
||||
# Папка зависимостей Composer
|
||||
/vendor
|
||||
|
||||
# Папка зависимостей Node.js (фронтенд)
|
||||
/node_modules
|
||||
/npm-debug.log
|
||||
/yarn-error.log
|
||||
|
||||
# Скомпилированные ассеты фронтенда
|
||||
/public/build
|
||||
/public/hot
|
||||
/public/storage
|
||||
|
||||
# Логи и временные файлы Laravel
|
||||
/storage/*.key
|
||||
/storage/framework/cache/data/*
|
||||
/storage/framework/sessions/*
|
||||
/storage/framework/testing/*
|
||||
/storage/framework/views/*
|
||||
/storage/logs/*
|
||||
|
||||
# Исключения для папок Laravel (чтобы Git сохранял саму структуру папок)
|
||||
!/storage/framework/cache/data/.gitignore
|
||||
!/storage/framework/sessions/.gitignore
|
||||
!/storage/framework/testing/.gitignore
|
||||
!/storage/framework/views/.gitignore
|
||||
!/storage/logs/.gitignore
|
||||
|
||||
# Файлы окружения IDE и операционных систем
|
||||
.idea/
|
||||
.vscode/
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
@@ -0,0 +1,25 @@
|
||||
.PHONY: up down install shell status logs
|
||||
|
||||
# Запуск контейнеров в фоновом режиме
|
||||
up:
|
||||
docker compose up -d
|
||||
|
||||
# Остановка и удаление контейнеров
|
||||
down:
|
||||
docker compose down
|
||||
|
||||
# Установка зависимостей Composer внутри контейнера web_server
|
||||
install:
|
||||
docker compose exec --workdir /var/www/html web composer install
|
||||
|
||||
# Быстрый вход в терминал веб-сервера
|
||||
shell:
|
||||
docker compose exec web bash
|
||||
|
||||
# Проверить статус контейнеров
|
||||
status:
|
||||
docker compose ps
|
||||
|
||||
# Посмотреть живые логи
|
||||
logs:
|
||||
docker compose logs -f
|
||||
@@ -1,10 +1,120 @@
|
||||
# ВДНХ Афиша API (Тестовое задание)
|
||||
|
||||
## Развертывание проекта
|
||||
1. `composer install`
|
||||
2. Настроить подключение к БД в `.env`
|
||||
3. `php artisan migrate:fresh --seed` (команда развернет БД, создаст тестового юзера, 11 категорий ВДНХ и 40 сгенерированных событий).
|
||||
Бэкенд-приложение для фильтрации и вывода афиши событий Выставки (ВДНХ) на основе предоставленного технического задания.
|
||||
|
||||
## 🛠 Стек технологий
|
||||
- **PHP:** 8.2 / 8.3
|
||||
- **Framework:** Laravel 12.x
|
||||
- **Database:** MySQL / PostgreSQL
|
||||
- **API Specification:** Postman (коллекция в корне проекта)
|
||||
|
||||
---
|
||||
|
||||
## 📋 Постановка задачи
|
||||
Разработать JSON API для Афиши событий (https://vdnh.ru/) со следующими требованиями:
|
||||
1. Метод API для фильтрации событий по нескольким категориям и промежутку дат.
|
||||
2. Реализация пагинации для списка событий.
|
||||
3. Наличие сидеров (Database Seeding) для наполнения тестовыми данными.
|
||||
4. Публикация на GitHub + предоставление коллекции для Postman.
|
||||
|
||||
## 🚀 Развертывание проекта
|
||||
|
||||
Выполните последовательно следующие команды в терминале:
|
||||
|
||||
1. **Клонирование репозитория и установка зависимостей:**
|
||||
```bash
|
||||
composer install
|
||||
```
|
||||
|
||||
2. **Настройка окружения:**
|
||||
```bash
|
||||
cp .env.example .env
|
||||
php artisan key:generate
|
||||
```
|
||||
*Отредактируйте файл `.env`, указав ваши доступы к базе данных (`DB_DATABASE`, `DB_USERNAME`, `DB_PASSWORD`).*
|
||||
|
||||
3. **Миграции и наполнение базы (Сидинг):**
|
||||
```bash
|
||||
php artisan migrate:fresh --seed
|
||||
```
|
||||
> 💡 **Что произойдет:** Команда полностью пересоздаст структуру БД и запустит сидеры, которые сгенерируют **11 реальных категорий** и **40 тестовых событий** со случайными датами для удобной проверки фильтрации.
|
||||
|
||||
4. **Пользователи:**
|
||||
- В базе уже есть сгенерирован "test@example.com" с паролем "password".
|
||||
- Чтобы использовать другие методы, находящиеся под защитой Sanctum, необходимо вызвать метод [/api/login](#1-авторизация-получение-токена)) и получить токен, который сохранится в переменную token в [POSTMAN](#-тестирование-в-postman).
|
||||
- Для генерации нового пользователя или изменения его пароля/токена можно воспользоваться консольными командами, описанными ниже.
|
||||
|
||||
## 💻 Консольные команды (CLI)
|
||||
|
||||
Для удобства управления пользователями и токенами добавлены кастомные Artisan-команды:
|
||||
|
||||
1. **Создание пользователя и генерация API-токена:**
|
||||
```bash
|
||||
php artisan user:create-api-user "Ivan Ivanov" ivan@example.com "secret123"
|
||||
```
|
||||
*Команда регистрирует нового пользователя в базе данных и сразу генерирует для него первый Sanctum-токен.*
|
||||
|
||||
2. **Сброс пароля и выдача нового токена:**
|
||||
```bash
|
||||
php artisan user:reset-password ivan@example.com "new_secret123"
|
||||
```
|
||||
*Команда находит пользователя по email, обновляет его пароль и выводит в консоль новый рабочий Sanctum-токен.*
|
||||
|
||||
|
||||
## 📡 Документация API (Эндпоинты)
|
||||
|
||||
*Для всех запросов обязательно передавайте заголовки:*
|
||||
- `Accept: application/json`
|
||||
- `Content-Type: application/json`
|
||||
|
||||
### 1. Авторизация (Получение токена)
|
||||
|
||||
* **URL:** `/api/login`
|
||||
* **Метод:** `POST`
|
||||
|
||||
#### Тело запроса (JSON):
|
||||
```json
|
||||
{
|
||||
"email": "user@example.com",
|
||||
"password": "your_password"
|
||||
}
|
||||
```
|
||||
*В ответе возвращаются данные пользователя и его `access_token` для авторизации в защищенных эндпоинтах.*
|
||||
|
||||
### 2. Выход из системы (Logout)
|
||||
* **URL:** `/api/logout`
|
||||
* **Метод:** `POST`
|
||||
* **Описание:** Деактивирует и удаляет текущий API-токен Sanctum, с которым был выполнен запрос. Заголовок Authorization c токеном обязателен.
|
||||
|
||||
|
||||
### 3. Получение данных пользователя
|
||||
* **URL:** `/api/user`
|
||||
* **Метод:** `GET`
|
||||
* **Описание:**: описание пользователя, заголовок Authorization c токеном обязателен.
|
||||
|
||||
### 4. Получение списка событий с фильтрацией и пагинацией
|
||||
|
||||
* **URL:** `/api/gigs`
|
||||
* **Метод:** `GET`
|
||||
* **Описание:** для метода необходим заголовок Authorization: Bearer {{token}}, который можно получить методом /login.
|
||||
|
||||
#### Параметры запроса (Query Parameters):
|
||||
| Параметр | Тип | Обязательный | Описание | Пример / Значение |
|
||||
| :--- | :--- | :--- | :--- | :--- |
|
||||
| `categories` | `array` | Нет | Массив ID или слагов категорий | `categories[]=1&categories[]=2` |
|
||||
| `date_from` | `string` | Нет | Начало диапазона дат (`YYYY-MM-DD`) | `2026-05-01` |
|
||||
| `date_to` | `string` | Нет | Конец диапазона дат (`YYYY-MM-DD`) | `2026-05-31` |
|
||||
| `page` | `integer` | Нет | Номер страницы пагинации | `2` |
|
||||
| `per_page` | `integer` | Нет | Количество элементов на страницу | `15` *(По умолчанию: `10`)* |
|
||||
|
||||
|
||||
#### Пример запроса:
|
||||
`GET /api/gigs?categories[]=1&categories[]=4&date_from=2026-02-01&date_to=2026-05-31`
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Тестирование в Postman
|
||||
|
||||
В корне репозитория находится файл коллекции для быстрого тестирования всех эндпоинтов: **`gigs_api_collection.json`**.
|
||||
|
||||
|
||||
## Эндпоинты
|
||||
- `GET /api/gigs` — Получение списка событий с пагинацией и фильтрами.
|
||||
- Для тестирования импортируйте файл `gigs_api_collection.json` в Postman.
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class ResetUserPassword extends Command
|
||||
{
|
||||
// Название и аргументы команды (email обязательный, password — опциональный)
|
||||
protected $signature = 'user:reset-password {email} {password?}';
|
||||
|
||||
// Описание команды для списка php artisan
|
||||
protected $description = 'Сбрасывает пароль пользователя по email и генерирует новый API-токен Sanctum';
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$email = $this->argument('email');
|
||||
$password = $this->argument('password');
|
||||
|
||||
// 1. Поиск пользователя
|
||||
$user = User::where('email', $email)->first();
|
||||
|
||||
if (!$user) {
|
||||
$this->error("Пользователь с email {$email} не найден.");
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
// 2. Генерация или использование переданного пароля
|
||||
if (!$password) {
|
||||
$password = Str::random(12); // Случайный пароль из 12 символов
|
||||
}
|
||||
|
||||
// 3. Обновление пароля в базе данных
|
||||
$user->password = Hash::make($password);
|
||||
$user->save();
|
||||
|
||||
// 4. Отзыв старых токенов (опционально, для безопасности)
|
||||
$user->tokens()->delete();
|
||||
|
||||
// 5. Создание нового токена Sanctum
|
||||
$token = $user->createToken('console_auth_token')->plainTextToken;
|
||||
|
||||
// 6. Вывод результата в консоль
|
||||
$this->info("Пароль для пользователя {$email} успешно изменен!");
|
||||
$this->line("Новый пароль: <comment>{$password}</comment>");
|
||||
$this->line("Новый Sanctum токен: <comment>{$token}</comment>");
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
class LoginController extends Controller
|
||||
{
|
||||
public function login(Request $request)
|
||||
{
|
||||
// 1. Валидация JSON-данных
|
||||
$validator = Validator::make($request->all(), [
|
||||
'email' => ['required', 'string', 'email'],
|
||||
'password' => ['required', 'string'],
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return response()->json([
|
||||
'status' => 'error',
|
||||
'message' => 'Validation failed',
|
||||
'errors' => $validator->errors()
|
||||
], 422);
|
||||
}
|
||||
|
||||
// 2. Поиск пользователя
|
||||
$user = User::where('email', $request->email)->first();
|
||||
|
||||
// 3. Проверка пароля
|
||||
if (!$user || !Hash::check($request->password, $user->password)) {
|
||||
return response()->json([
|
||||
'status' => 'error',
|
||||
'message' => 'Invalid credentials'
|
||||
], 401);
|
||||
}
|
||||
|
||||
// 4. Генерация нового токена Sanctum
|
||||
$token = $user->createToken('auth_token')->plainTextToken;
|
||||
|
||||
return response()->json([
|
||||
'status' => 'success',
|
||||
'message' => 'Logged in successfully',
|
||||
'data' => [
|
||||
'user' => $user,
|
||||
'token_type' => 'Bearer',
|
||||
'access_token' => $token,
|
||||
]
|
||||
], 200);
|
||||
}
|
||||
|
||||
public function logout(Request $request)
|
||||
{
|
||||
// Удаление текущего токена, с которым пришел пользователь
|
||||
$request->user()->currentAccessToken()->delete();
|
||||
|
||||
return response()->json([
|
||||
'status' => 'success',
|
||||
'message' => 'Logged out successfully'
|
||||
], 200);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\ORM\Category;
|
||||
|
||||
class CategoryController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
// Получаем все категории, отсортированные по имени
|
||||
$categories = Category::orderBy('name', 'asc')->get();
|
||||
|
||||
return response()->json([
|
||||
'status' => 'success',
|
||||
'message' => 'Categories retrieved successfully',
|
||||
'data' => $categories
|
||||
], 200);
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use App\Http\Requests\GetGigsRequest;
|
||||
use App\Http\Resources\GigResource;
|
||||
use App\Models\ORM\Gig;
|
||||
@@ -11,32 +12,40 @@ use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
|
||||
|
||||
class GigsController extends Controller
|
||||
{
|
||||
CONST PER_PAGE = 10;
|
||||
|
||||
public function index(GetGigsRequest $request): AnonymousResourceCollection
|
||||
{
|
||||
// Используем eager loading (with), чтобы избежать проблемы N+1 запросов к категориям
|
||||
// 1. Берем СТРОГО валидированные данные
|
||||
$validated = $request->validated();
|
||||
|
||||
// Используем eager loading (with), чтобы избежать проблемы N+1
|
||||
$query = Gig::with('categories');
|
||||
|
||||
// Фильтрация по нескольким категориям (Many-to-Many)
|
||||
if ($request->filled('categories')) {
|
||||
$query->whereHas('categories', function ($q) use ($request) {
|
||||
$q->whereIn('categories.id', $request->input('categories'));
|
||||
if (!empty($validated['categories'])) {
|
||||
$query->whereHas('categories', function ($q) use ($validated) {
|
||||
$q->whereIn('categories.id', $validated['categories']);
|
||||
});
|
||||
}
|
||||
|
||||
// Фильтрация по промежутку дат
|
||||
if ($request->filled('date_from')) {
|
||||
$query->whereDate('event_date', '>=', $request->input('date_from'));
|
||||
if (!empty($validated['date_from'])) {
|
||||
$query->where('event_date', '>=', Carbon::parse($validated['date_from'])->startOfDay());
|
||||
}
|
||||
|
||||
if ($request->filled('date_to')) {
|
||||
$query->whereDate('event_date', '<=', $request->input('date_to'));
|
||||
if (!empty($validated['date_to'])) {
|
||||
$query->where('event_date', '<=', Carbon::parse($validated['date_to'])->endOfDay());
|
||||
}
|
||||
|
||||
// Получаем per_page из валидированных данных, либо берем дефолт
|
||||
$per_page = $validated['per_page'] ?? self::PER_PAGE;
|
||||
|
||||
// Сортируем события: сначала ближайшие
|
||||
$query->orderBy('event_date', 'asc');
|
||||
|
||||
// Пагинация по 10 элементов на страницу
|
||||
$gigs = $query->paginate(10);
|
||||
// Пагинация
|
||||
$gigs = $query->paginate((int)$per_page);
|
||||
|
||||
return GigResource::collection($gigs);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ class GetGigsRequest extends FormRequest
|
||||
'categories.*' => ['integer', 'exists:categories,id'], // Проверяем, что ID категорий существуют
|
||||
'date_from' => ['nullable', 'date', 'date_format:Y-m-d'],
|
||||
'date_to' => ['nullable', 'date', 'date_format:Y-m-d', 'after_or_equal:date_from'],
|
||||
'per_page' => ['nullable', 'integer', 'min:1', 'max:50'],
|
||||
'page' => ['nullable', 'integer', 'min:1'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
services:
|
||||
# Веб-сервер Apache + PHP
|
||||
web:
|
||||
image: webdevops/php-apache:8.2
|
||||
container_name: web_server
|
||||
ports:
|
||||
- "8080:80"
|
||||
volumes:
|
||||
- .:/var/www/html
|
||||
environment:
|
||||
- WEB_DOCUMENT_ROOT=/var/www/html/public
|
||||
networks:
|
||||
- lamp-network
|
||||
|
||||
# База данных MySQL
|
||||
db:
|
||||
image: mysql:8.0
|
||||
container_name: db_server
|
||||
restart: always
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: gigs_api
|
||||
MYSQL_DATABASE: gigs_api
|
||||
MYSQL_USER: gigs_api
|
||||
MYSQL_PASSWORD: gigs_api
|
||||
ports:
|
||||
- "3306:3306"
|
||||
volumes:
|
||||
- db_data:/var/lib/mysql
|
||||
networks:
|
||||
- lamp-network
|
||||
|
||||
networks:
|
||||
lamp-network:
|
||||
driver: bridge
|
||||
|
||||
volumes:
|
||||
db_data:
|
||||
|
||||
|
||||
@@ -0,0 +1,242 @@
|
||||
{
|
||||
"info": {
|
||||
"_postman_id": "2e44665b-610d-4022-9d35-c5d4ba3182cc",
|
||||
"name": "Gigs API",
|
||||
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
|
||||
"_exporter_id": "55246252",
|
||||
"_collection_link": "https://blacksnowman-7b5016ee-929558.postman.co/workspace/RoadCrew's-Workspace~f1936404-0a84-43c8-873a-5676fefcbc04/collection/55246252-2e44665b-610d-4022-9d35-c5d4ba3182cc?action=share&source=collection_link&creator=55246252"
|
||||
},
|
||||
"item": [
|
||||
{
|
||||
"name": "Get basic user",
|
||||
"request": {
|
||||
"auth": {
|
||||
"type": "noauth"
|
||||
},
|
||||
"method": "GET",
|
||||
"header": [
|
||||
{
|
||||
"key": "Authorization",
|
||||
"value": "Bearer {{token}}",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"key": "Accept",
|
||||
"value": "application/json",
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"url": {
|
||||
"raw": "{{server}}/api/user",
|
||||
"host": [
|
||||
"{{server}}"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"user"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "Get gigs",
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"exec": [
|
||||
"pm.test(\"Status code is 200\", function () {",
|
||||
" pm.response.to.have.status(200);",
|
||||
"});"
|
||||
],
|
||||
"type": "text/javascript",
|
||||
"packages": {},
|
||||
"requests": {}
|
||||
}
|
||||
}
|
||||
],
|
||||
"request": {
|
||||
"auth": {
|
||||
"type": "noauth"
|
||||
},
|
||||
"method": "GET",
|
||||
"header": [
|
||||
{
|
||||
"key": "Authorization",
|
||||
"value": "Bearer {{token}}",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"key": "Accept",
|
||||
"value": "application/json",
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"url": {
|
||||
"raw": "{{server}}/api/gigs?categories[]=1&categories[]=4&date_from=2026-02-01&date_to=2026-05-31",
|
||||
"host": [
|
||||
"{{server}}"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"gigs"
|
||||
],
|
||||
"query": [
|
||||
{
|
||||
"key": "categories[]",
|
||||
"value": "1"
|
||||
},
|
||||
{
|
||||
"key": "categories[]",
|
||||
"value": "4"
|
||||
},
|
||||
{
|
||||
"key": "date_from",
|
||||
"value": "2026-02-01"
|
||||
},
|
||||
{
|
||||
"key": "date_to",
|
||||
"value": "2026-05-31"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "Login",
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"exec": [
|
||||
"// 1. Проверяем, что сервер вернул успешный HTTP-статус (200 OK)",
|
||||
"pm.test(\"Status code is 200\", function () {",
|
||||
" pm.response.to.have.status(200);",
|
||||
"});",
|
||||
"",
|
||||
"// 2. Парсим JSON-ответ и сохраняем токен",
|
||||
"if (pm.response.code === 200) {",
|
||||
" const responseJson = pm.response.json();",
|
||||
" ",
|
||||
" // Проверяем, что в ответе есть статус 'success' и сам токен",
|
||||
" if (responseJson.status === \"success\" && responseJson.data && responseJson.data.access_token) {",
|
||||
" const token = responseJson.data.access_token;",
|
||||
" ",
|
||||
" // Сохраняем токен в ГЛОБАЛЬНУЮ переменную",
|
||||
" pm.globals.set(\"token\", token);",
|
||||
" ",
|
||||
" console.log(\"Токен успешно сохранен в глобальную переменную 'token':\", token);",
|
||||
" } else {",
|
||||
" console.error(\"Не удалось найти 'access_token' в ответе сервера\");",
|
||||
" }",
|
||||
"}"
|
||||
],
|
||||
"type": "text/javascript",
|
||||
"packages": {},
|
||||
"requests": {}
|
||||
}
|
||||
}
|
||||
],
|
||||
"request": {
|
||||
"auth": {
|
||||
"type": "noauth"
|
||||
},
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{
|
||||
"key": "Accept",
|
||||
"value": "application/json",
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{ \n \"email\": \"test@example.com\",\n \"password\": \"123456\"\n}",
|
||||
"options": {
|
||||
"raw": {
|
||||
"language": "json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{server}}/api/login",
|
||||
"host": [
|
||||
"{{server}}"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"login"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "Loout",
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"exec": [
|
||||
"// 1. Проверяем, что сервер вернул успешный HTTP-статус (200 OK)",
|
||||
"pm.test(\"Status code is 200\", function () {",
|
||||
" pm.response.to.have.status(200);",
|
||||
"});",
|
||||
"",
|
||||
"// 2. Парсим JSON-ответ и сохраняем токен",
|
||||
"if (pm.response.code === 200) {",
|
||||
" const responseJson = pm.response.json();",
|
||||
" ",
|
||||
" // Проверяем, что в ответе есть статус 'success' и сам токен",
|
||||
" if (responseJson.status === \"success\" && responseJson.data && responseJson.data.access_token) {",
|
||||
" const token = responseJson.data.access_token;",
|
||||
" ",
|
||||
" // Сохраняем токен в ГЛОБАЛЬНУЮ переменную",
|
||||
" pm.globals.set(\"token\", token);",
|
||||
" ",
|
||||
" console.log(\"Токен успешно сохранен в глобальную переменную 'token':\", token);",
|
||||
" } else {",
|
||||
" console.error(\"Не удалось найти 'access_token' в ответе сервера\");",
|
||||
" }",
|
||||
"}"
|
||||
],
|
||||
"type": "text/javascript",
|
||||
"packages": {},
|
||||
"requests": {}
|
||||
}
|
||||
}
|
||||
],
|
||||
"request": {
|
||||
"auth": {
|
||||
"type": "noauth"
|
||||
},
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{
|
||||
"key": "Accept",
|
||||
"value": "application/json",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"key": "Authorization",
|
||||
"value": "Bearer {{token}}",
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"url": {
|
||||
"raw": "{{server}}/api/logout",
|
||||
"host": [
|
||||
"{{server}}"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"logout"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,12 +1,27 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use App\Http\Controllers\GigsController;
|
||||
use App\Http\Controllers\Auth\LoginController;
|
||||
use App\Http\Controllers\CategoryController;
|
||||
|
||||
// Публичный маршрут для входа
|
||||
Route::post('/login', [ LoginController::class, 'login' ]);
|
||||
|
||||
Route::get('/user', function (Request $request) {
|
||||
return $request->user();
|
||||
})->middleware('auth:sanctum');
|
||||
|
||||
Route::middleware('auth:sanctum')->group(function () {
|
||||
// Получение списка категорий
|
||||
Route::get('/categories', [ CategoryController::class, 'index' ]);
|
||||
|
||||
// Получение списка событий (с фильтрами и пагинацией)
|
||||
Route::get('/gigs', [ GigsController::class, 'index' ]);
|
||||
|
||||
// Выход из системы (отзыв токена)
|
||||
Route::post('/logout', [ LoginController::class, 'logout' ]);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user