first commit

This commit is contained in:
2025-02-24 12:12:17 +03:00
parent bcef02b2ba
commit 685c734f6e
22 changed files with 627 additions and 4 deletions

View File

@@ -0,0 +1,317 @@
<?php
namespace App\Http\Controllers;
use App\Models\LunarMission;
use App\Models\SpaceFlight;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
class DefaultController extends Controller
{
private const FORBIDDEN = '{
"message": "Forbidden for you"
}';
private const NOT_FOUND = '
{
"message": "Not found",
"code": 404
}
';
public function getGagarinFlight(Request $request)
{
if (!$this->checkAuth($request)) {
return response()->json(json_decode(self::FORBIDDEN));
}
$responseValue = '{
"data": [
{
"mission": {
"name": "Восток 1",
"launch_details": {
"launch_date": "1961-04-12",
"launch_site": {
"name": "Космодром Байконур",
"location": {
"latitude": "45.9650000",
"longitude": "63.3050000"
}
}
},
"flight_duration": {
"hours": 1,
"minutes": 48
},
"spacecraft": {
"name": "Восток 3KA",
"manufacturer": "OKB-1",
"crew_capacity": 1
}
},
"landing": {
"date": "1961-04-12",
"site": {
"name": "Смеловка",
"country": "СССР",
"coordinates": {
"latitude": "51.2700000",
"longitude": "45.9970000"
}
},
"details": {
"parachute_landing": true,
"impact_velocity_mps": 7
}
},
"cosmonaut": {
"name": "Юрий Гагарин",
"birthdate": "1934-03-09",
"rank": "Старший лейтенант",
"bio": {
"early_life": "Родился в Клушино, Россия.",
"career": "Отобран в отряд космонавтов в 1960 году...",
"post_flight": "Стал международным героем."
}
}
}
]
}';
return response()->json(json_decode($responseValue));
}
public function getFlight()
{
$responseValue = '{
"data": {
"name": "Аполлон-11",
"crew_capacity": 3,
"cosmonaut": [
{
"name": "Нил Армстронг",
"role": "Командир"
},
{
"name": "Базз Олдрин",
"role": "Пилот лунного модуля"
},
{
"name": "Майкл Коллинз",
"role": "Пилот командного модуля"
}
],
"launch_details": {
"launch_date": "1969-07-16",
"launch_site": {
"name": "Космический центр имени Кеннеди",
"latitude": "28.5721000",
"longitude": "-80.6480000"
}
},
"landing_details": {
"landing_date": "1969-07-20",
"landing_site": {
"name": "Море спокойствия",
"latitude": "0.6740000",
"longitude": "23.4720000"
}
}
}
}';
return response()->json(json_decode($responseValue));
}
public function addLunarMission(Request $request)
{
$validated = Validator::make($request->all(), [
'mission' => 'required',
'mission.name' => 'required|regex:/^[A-ZА-ЯЁ].*$/u',
'mission.launch_details' => 'required',
'mission.launch_details.launch_date' => ['required', Rule::date()->format('Y-m-d')],
'mission.launch_details.launch_site' => 'required',
'mission.launch_details.launch_site.name' => 'required',
'mission.launch_details.launch_site.location' => 'required',
'mission.launch_details.launch_site.location.latitude' => 'required|decimal:1,10',
'mission.launch_details.launch_site.location.longitude' => 'required|decimal:1,10',
'mission.landing_details' => 'required',
'mission.landing_details.landing_date' => ['required', Rule::date()->format('Y-m-d')],
'mission.landing_details.landing_site' => 'required',
'mission.landing_details.landing_site.name' => 'required',
'mission.landing_details.landing_site.coordinates' => 'required',
'mission.landing_details.landing_site.coordinates.latitude' => 'required|decimal:1,10',
'mission.landing_details.landing_site.coordinates.longitude' => 'required|decimal:1,10',
'mission.spacecraft' => 'required',
'mission.spacecraft.command_module' => 'required',
'mission.spacecraft.lunar_module' => 'required',
'mission.spacecraft.crew' => 'required|array',
'mission.spacecraft.crew.*.name' => 'required',
'mission.spacecraft.crew.*.role' => 'required',
],
[
'name.regex' => 'Поле :attribute должно начинаться с большой буквы.',
]
);
if ($validated->errors()->isNotEmpty()) {
$error = '{
"error": {
"code": 422,
"message": "Not valid"
}
}';
$error = json_decode($error);
$error->error->errors = $validated->errors()->getMessages();
return response()->json($error);
}
$mission = new LunarMission();
$mission->missions = $request->all();
$mission->save();
$response = '{
"data": {
"code": 201,
"message": "Миссия добавлена"
}
}';
return response()->json(json_decode($response), 201);
}
public function getLunarMissions()
{
$missions = LunarMission::all('missions')->map(fn(LunarMission $mission) => $mission->missions);
return response()->json($missions->all());
}
public function deleteLunarMission(Request $request, int $id)
{
$lm = LunarMission::find($id);
if (!$lm) {
return response()->json(json_decode(self::NOT_FOUND));
}
return response()->json(null, 204);
}
public function editLunarMission(Request $request, int $id)
{
$mission = LunarMission::find($id);
if (!$mission) {
return response()->json(json_decode(self::NOT_FOUND));
}
$mission->missions = $request->all();
$mission->save();
$response = '{
"data": {
"code": 200,
"message": "Миссия обновлена"
}
}';
return response()->json(json_decode($response));
}
public function addSpaceFlight(Request $request)
{
$mission = new SpaceFlight();
$mission->flight = $request->all();
$mission->save();
$response = '{
"data": {
"code": 201,
"message": "Космический полет создан"
}
}';
return response()->json(json_decode($response), 201);
}
public function getSpaceFlight()
{
$flights = SpaceFlight::query()->where('flight->seats_available', '>', 0)->get();
$flights = $flights->map(fn(SpaceFlight $flight) => $flight->flight);
return response()->json($flights->all());
}
public function bookFlight(Request $request)
{
$number = $request->get('flight_number');
$flight = SpaceFlight::query()->where('flight->flight_number', $number)->where('flight->seats_available', '> 0')->first();
$response = '{
"data": {
"code": 404,
"message": "Полет не найден"
}
}';
if (!$flight) {
return response()->json(json_decode($response), 404);
}
$flightInfo = $flight->flight;
$flightInfo['seats_available'] = $flightInfo['seats_available'] - 1;
$flight->flight = $flightInfo;
$flight->save();
$response = '{
"data": {
"code": 201,
"message": "Рейс забронирован"
}
}';
return response()->json(json_decode($response), 201);
}
public function search(Request $request)
{
$query = $request->get('query');
// для поиска по массиву crew, находящимся в объекте spacecraft
// который в свою очередь находится в объекте mission и в папке missions используется следующий запрос
$result = LunarMission::query()
->orWhereRaw("missions->'$.mission.spacecraft.crew[*].name' LIKE '%$query%'")
->get();
// 'это костыль, для того чтобы подсветить, что найден пилот и чтоб не возиться с джойнами в SQL запросах
$result2 = LunarMission::query()
->orWhere('missions->mission->name', 'LIKE', "%$query%")
->get();
$result = $result->map(function (LunarMission $lunarMission) use ($query) {
$mission = $lunarMission->missions;
return [
"type" => 'Миссия',
"name" => $mission['mission']['name'],
"launch_date" => $mission['mission']['launch_details']['launch_date'],
"landing_date" => $mission['mission']['landing_details']['landing_date'],
'crew' => $mission['mission']['spacecraft']['crew'],
'landing_site' => $mission['mission']['landing_details']['landing_site']['name']
];
});
$result2 = $result2->map(function (LunarMission $lunarMission) use ($query) {
$mission = $lunarMission->missions;
return [
"type" => "Пилот",
"name" => $mission['mission']['name'],
"launch_date" => $mission['mission']['launch_details']['launch_date'],
"landing_date" => $mission['mission']['landing_details']['landing_date'],
'crew' => $mission['mission']['spacecraft']['crew'],
'landing_site' => $mission['mission']['landing_details']['landing_site']['name']
];
});
return response()->json(['data' => $result->merge($result2)->all()]);
}
private function checkAuth(Request $request)
{
$token = str_replace('Bearer ', '', $request->header('Authorization'));
return User::where('token', $token)->first();
}
}

View File

@@ -0,0 +1,12 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class LunarMission extends Model
{
protected $casts = [
'missions' => 'array'
];
}

View File

@@ -0,0 +1,12 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class SpaceFlight extends Model
{
protected $casts = [
'flight' => 'array'
];
}

View File

@@ -7,8 +7,10 @@ use Illuminate\Foundation\Configuration\Middleware;
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
web: __DIR__.'/../routes/web.php',
api: __DIR__.'/../routes/api.php',
commands: __DIR__.'/../routes/console.php',
health: '/up',
apiPrefix: 'api-kosmos'
)
->withMiddleware(function (Middleware $middleware) {
//

0
bootstrap/cache/.gitignore vendored Normal file → Executable file
View File

View File

@@ -8,6 +8,7 @@
"php": "^8.2",
"guzzlehttp/guzzle": "^7.2",
"laravel/framework": "^11.0",
"laravel/sanctum": "^4.0",
"laravel/tinker": "^2.9"
},
"require-dev": {

72
composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "f2046e54e0e5a393b33d4a440a1b68d5",
"content-hash": "8fa2d0dd5b833adf5981da170259333f",
"packages": [
{
"name": "brick/math",
@@ -1328,6 +1328,70 @@
},
"time": "2025-01-24T15:41:01+00:00"
},
{
"name": "laravel/sanctum",
"version": "v4.0.8",
"source": {
"type": "git",
"url": "https://github.com/laravel/sanctum.git",
"reference": "ec1dd9ddb2ab370f79dfe724a101856e0963f43c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/sanctum/zipball/ec1dd9ddb2ab370f79dfe724a101856e0963f43c",
"reference": "ec1dd9ddb2ab370f79dfe724a101856e0963f43c",
"shasum": ""
},
"require": {
"ext-json": "*",
"illuminate/console": "^11.0|^12.0",
"illuminate/contracts": "^11.0|^12.0",
"illuminate/database": "^11.0|^12.0",
"illuminate/support": "^11.0|^12.0",
"php": "^8.2",
"symfony/console": "^7.0"
},
"require-dev": {
"mockery/mockery": "^1.6",
"orchestra/testbench": "^9.0|^10.0",
"phpstan/phpstan": "^1.10",
"phpunit/phpunit": "^11.3"
},
"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": "2025-01-26T19:34:36+00:00"
},
{
"name": "laravel/serializable-closure",
"version": "v2.0.2",
@@ -8532,12 +8596,12 @@
],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"stability-flags": {},
"prefer-stable": true,
"prefer-lowest": false,
"platform": {
"php": "^8.2"
},
"platform-dev": [],
"plugin-api-version": "2.3.0"
"platform-dev": {},
"plugin-api-version": "2.6.0"
}

83
config/sanctum.php Normal file
View File

@@ -0,0 +1,83 @@
<?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 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,
],
];

View File

@@ -0,0 +1,28 @@
<?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('lunar_missions', function (Blueprint $table) {
$table->id();
$table->jsonb('missions')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('lunar_missions');
}
};

View File

@@ -0,0 +1,28 @@
<?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('space_flights', function (Blueprint $table) {
$table->id();
$table->timestamps();
$table->jsonb('flight')->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('space_flights');
}
};

View File

@@ -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->string('name');
$table->string('token', 64)->unique();
$table->text('abilities')->nullable();
$table->timestamp('last_used_at')->nullable();
$table->timestamp('expires_at')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('personal_access_tokens');
}
};

View File

@@ -0,0 +1,28 @@
<?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::table('users', function (Blueprint $table) {
$table->string('token')->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
//
});
}
};

15
routes/api.php Normal file
View File

@@ -0,0 +1,15 @@
<?php
use Illuminate\Support\Facades\Route;
Route::get('/gagarin-flight', [\App\Http\Controllers\DefaultController::class, 'getGagarinFlight']);
Route::get('flight', [\App\Http\Controllers\DefaultController::class, 'getFlight']);
Route::post('lunar-missions', [\App\Http\Controllers\DefaultController::class, 'addLunarMission']);
Route::get('lunar-missions', [\App\Http\Controllers\DefaultController::class, 'getLunarMissions']);
Route::delete('lunar-missions/{id}', [\App\Http\Controllers\DefaultController::class, 'deleteLunarMission']);
Route::patch('lunar-missions/{id}', [\App\Http\Controllers\DefaultController::class, 'editLunarMission']);
Route::post('space-flights', [\App\Http\Controllers\DefaultController::class, 'addSpaceFlight']);
Route::get('space-flights', [\App\Http\Controllers\DefaultController::class, 'getSpaceFlight']);
Route::get('space-flights', [\App\Http\Controllers\DefaultController::class, 'getSpaceFlight']);
Route::post('book-flight', [\App\Http\Controllers\DefaultController::class, 'bookFlight']);
Route::get('search', [\App\Http\Controllers\DefaultController::class, 'search']);

0
storage/app/.gitignore vendored Normal file → Executable file
View File

0
storage/app/public/.gitignore vendored Normal file → Executable file
View File

0
storage/framework/.gitignore vendored Normal file → Executable file
View File

0
storage/framework/cache/.gitignore vendored Normal file → Executable file
View File

0
storage/framework/cache/data/.gitignore vendored Normal file → Executable file
View File

0
storage/framework/sessions/.gitignore vendored Normal file → Executable file
View File

0
storage/framework/testing/.gitignore vendored Normal file → Executable file
View File

0
storage/framework/views/.gitignore vendored Normal file → Executable file
View File

0
storage/logs/.gitignore vendored Normal file → Executable file
View File