Skip to content

Commit

Permalink
Merge branch 'refs/heads/main' into #10-regulations-for-escooters
Browse files Browse the repository at this point in the history
# Conflicts:
#	composer.json
#	composer.lock
#	lang/pl.json
#	routes/api.php
  • Loading branch information
JakubKermes committed May 8, 2024
2 parents f28c1d1 + 5592292 commit 8ed2d61
Show file tree
Hide file tree
Showing 29 changed files with 1,708 additions and 460 deletions.
15 changes: 15 additions & 0 deletions app/Exceptions/ExceptionHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Illuminate\Foundation\Exceptions\Handler;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Request;
use Illuminate\Validation\ValidationException;
use Inertia\Inertia;
use Sentry\Laravel\Integration;
Expand All @@ -29,6 +30,13 @@ public function register(): void
public function render($request, Throwable $exception)
{
if ($exception instanceof ValidationException) {
if (Request::is("api/*")) {
return response()->json([
"message" => $exception->getMessage(),
"errors" => $exception->errors(),
], Response::HTTP_UNPROCESSABLE_ENTITY);
}

return back()->withErrors($exception->errors());
}

Expand Down Expand Up @@ -78,6 +86,13 @@ public function render($request, Throwable $exception)
break;
}

if (Request::is("api/*")) {
return response()->json([
"statusTitle" => $statusTitle,
"statusDescription" => $statusDescription,
], $statusCode);
}

return Inertia::render("Error", [
"statusTitle" => $statusTitle,
"statusDescription" => $statusDescription,
Expand Down
94 changes: 58 additions & 36 deletions app/Http/Controllers/Api/Admin/ProviderController.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,58 +5,44 @@
namespace App\Http\Controllers\Api\Admin;

use App\Http\Controllers\Controller;
use App\Http\Requests\ProviderRequest;
use App\Http\Requests\ApiProviderRequest;
use App\Http\Resources\ProviderResource;
use App\Models\Provider;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Storage;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\Response;

class ProviderController extends Controller
{
public const ITEMS_PER_PAGE = 15;

public function index(): JsonResponse
{
$providers = Provider::query()
->search("name")
->orderByName()
->orderByTimeRange()
->paginate(self::ITEMS_PER_PAGE)
->withQueryString();
$providers = Provider::all();

return response()->json([
"providers" => ProviderResource::collection($providers),
]);
}

public function store(ProviderRequest $request): JsonResponse
public function store(ApiProviderRequest $request): JsonResponse
{
Provider::query()->create($request->validated());

$fileName = $this->getFilename($request->name, $request->file("file"));
$fileContents = $request->file("file")->get();
$provider = Provider::create($request->validated());

Storage::disk("public")->put("providers/" . $fileName, $fileContents);
if ($request->has("file")) {
if (!$this->processProviderImage($request->file, $provider->name)) {
return response()->json(["message" => __("The image must be 150x100 pixels.")], Response::HTTP_UNPROCESSABLE_ENTITY);
}
}

return response()->json(["message" => __("Provider created successfully.")], 201);
return response()->json(["message" => __("Provider created successfully.")], Response::HTTP_CREATED);
}

public function update(ProviderRequest $request, Provider $provider): JsonResponse
public function update(ApiProviderRequest $request, Provider $provider): JsonResponse
{
$provider->update($request->validated());

$imageName = $this->getFilename($request->name, $request->file("file"));
$storageImagePath = storage_path("app/public/providers/" . $imageName);
$resourceImagePath = resource_path("providers/" . $imageName);
$imageContents = $request->file("file")->get();

if (file_exists($resourceImagePath)) {
file_put_contents($resourceImagePath, $imageContents);
Storage::put($storageImagePath, file_get_contents($imageContents));
} else {
Storage::put($storageImagePath, file_get_contents($imageContents));
if ($request->has("file")) {
$this->replaceProviderImage($request->file, $provider->name);
}

return response()->json(["message" => __("Provider updated successfully.")]);
Expand All @@ -65,19 +51,15 @@ public function update(ProviderRequest $request, Provider $provider): JsonRespon
public function destroy(Provider $provider): JsonResponse
{
$provider->delete();
$imagePath = storage_path("app/public/providers/" . strtolower($provider["name"]) . ".png");
$imagePath = $this->providerImagePath($provider->name);
File::delete($imagePath);

return response()->json(["message" => __("Provider deleted successfully.")]);
}

public function showLogo(string $filename): JsonResponse
{
$imagePath = storage_path("app/public/providers/" . $filename);

if (!file_exists($imagePath)) {
$imagePath = storage_path("app/public/providers/unknown.png");
}
$imagePath = $this->providerImagePath($filename, true);

$imageData = base64_encode(file_get_contents($imagePath));

Expand All @@ -87,8 +69,48 @@ public function showLogo(string $filename): JsonResponse
]);
}

private function getFilename(string $name, UploadedFile $file): string
private function providerImagePath(string $name, bool $useDefault = false): string
{
return strtolower($name) . "." . $file->getClientOriginalExtension();
$path = storage_path("app/public/providers/" . strtolower($name) . ".png");

return file_exists($path) || !$useDefault ? $path : storage_path("app/public/providers/unknown.png");
}

private function processProviderImage(string $encodedFile, string $name): bool
{
[$decodedFile, $mimeType] = $this->decodeFile($encodedFile);

if (!$this->validateImageDimensions($decodedFile)) {
return false;
}

Storage::disk("public")->put("providers/" . strtolower($name) . "." . $mimeType, $decodedFile);

return true;
}

private function replaceProviderImage(string $encodedFile, string $name): void
{
$oldImagePath = $this->providerImagePath($name);
Storage::disk("public")->delete($oldImagePath);
$this->processProviderImage($encodedFile, $name);
}

private function decodeFile(string $encodedFile): array
{
preg_match('/^data:image\/(\w+);base64,/', $encodedFile, $matches);
$decodedFile = base64_decode(explode(",", $encodedFile)[1] ?? "", true);

return [$decodedFile, $matches[1] ?? "png"];
}

private function validateImageDimensions(string $decodedFile): bool
{
$imageResource = imagecreatefromstring($decodedFile);
$width = imagesx($imageResource);
$height = imagesy($imageResource);
imagedestroy($imageResource);

return $width === 150 && $height === 100;
}
}
16 changes: 9 additions & 7 deletions app/Http/Controllers/Api/AuthController.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public function store(RegisterRequest $request): JsonResponse

return response()->json([
"message" => __("User created."),
]);
], Response::HTTP_CREATED);
}

public function login(LoginRequest $request): JsonResponse
Expand All @@ -42,14 +42,15 @@ public function login(LoginRequest $request): JsonResponse
"password" => $request->password,
], $remember)) {
$user = Auth::user();
$user_id = (string)Auth::id();
$userId = (string)Auth::id();

$token_abilities = $this->getUserAbilities($user);

$token = $user->createToken($user_id, $token_abilities)->plainTextToken;
$token = $user->createToken($userId, $token_abilities)->plainTextToken;

return response()->json([
$token_abilities,
"abilities" => $token_abilities,
"userId" => $userId,
"access_token" => $token,
]);
}
Expand Down Expand Up @@ -90,16 +91,17 @@ public function handleProviderRedirect(string $provider): JsonResponse
]);
$token_abilities = $this->getUserAbilities($user);

$user_id = $user->id->toString();
$token = $user->createToken($user_id, $token_abilities)->plainTextToken;
$userId = (string)$user->id;
$token = $user->createToken($userId, $token_abilities)->plainTextToken;

return response()->json([
"access_token" => $token,
"userId" => $userId,
]);
} catch (Exception $e) {
return response()->json([
"message" => __("Login failed."),
]);
], Response::HTTP_UNAUTHORIZED);
}
}

Expand Down
3 changes: 2 additions & 1 deletion app/Http/Controllers/Api/ChangeLocaleController.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use App\Http\Controllers\Controller;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class ChangeLocaleController extends Controller
{
Expand All @@ -22,6 +23,6 @@ public function __invoke(Request $request, string $locale): JsonResponse

return response()->json([
"message" => __("Error changing the language."),
]);
], Response::HTTP_UNPROCESSABLE_ENTITY);
}
}
3 changes: 2 additions & 1 deletion app/Http/Controllers/Api/CityOpinionController.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use App\Http\Requests\CityOpinionRequest;
use App\Models\CityOpinion;
use Illuminate\Http\JsonResponse;
use Symfony\Component\HttpFoundation\Response;

class CityOpinionController extends Controller
{
Expand All @@ -19,7 +20,7 @@ public function store(CityOpinionRequest $request): JsonResponse

return response()->json([
"message" => __("Opinion added successfully."),
]);
], Response::HTTP_CREATED);
}

public function update(CityOpinionRequest $request, CityOpinion $cityOpinion): JsonResponse
Expand Down
8 changes: 8 additions & 0 deletions app/Http/Controllers/Api/CityProviderController.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use App\Services\DataImporterService;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\JsonResponse;
use Symfony\Component\HttpFoundation\Response;

class CityProviderController extends Controller
{
Expand Down Expand Up @@ -47,6 +48,13 @@ public function index(): JsonResponse

public function update(CityProviderService $service, CityProviderRequest $request, City $city): JsonResponse
{
foreach ($request->providerNames as $providerName) {
if (Provider::query()->where("name", $providerName)->doesntExist()) {
return response()->json([
"message" => __("Provider does not exist."),
], Response::HTTP_UNPROCESSABLE_ENTITY);
}
}
$service->updateProvider($request->providerNames, $city);

return response()->json([
Expand Down
8 changes: 8 additions & 0 deletions app/Http/Controllers/Api/FavoritesController.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@
use App\Http\Controllers\Controller;
use App\Http\Resources\CityResource;
use App\Http\Resources\ProviderResource;
use App\Models\City;
use App\Models\Favorites;
use App\Models\Provider;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class FavoritesController extends Controller
{
Expand All @@ -35,6 +37,12 @@ public function store(Request $request): JsonResponse
$cityId = $request->input("city_id");
$userId = $request->user()?->id;

if (City::query()->where("id", $cityId)->doesntExist() || !$cityId) {
return response()->json([
"message" => "City not found.",
], Response::HTTP_BAD_REQUEST);
}

$favorite = Favorites::firstOrCreate([
"user_id" => $userId,
"city_id" => $cityId,
Expand Down
35 changes: 35 additions & 0 deletions app/Http/Requests/ApiProviderRequest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

declare(strict_types=1);

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
use Illuminate\Validation\Rules\Unique;

class ApiProviderRequest extends FormRequest
{
public function rules(): array
{
return [
"name" => ["required", "string", "regex:/^[A-Z\s]/", "max:100", $this->uniqueRuleForProvider("name")],
"color" => ["required", "string", "size:7"],
"url" => ["nullable", "url"],
"android_url" => ["nullable", "url"],
"ios_url" => ["nullable", "url"],
"file" => [
"required",
"string",
"regex:/^data:image\/(png);base64,[a-zA-Z0-9+\/]+=*$/",
],
];
}

protected function uniqueRuleForProvider(string $column): Unique
{
$currentProviderId = $this->route(param: "provider");

return Rule::unique(table: "providers", column: $column)->ignore($currentProviderId);
}
}
2 changes: 2 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@
"require": {
"php": "^8.2",
"ext-dom": "*",
"ext-gd": "*",
"ext-pdo": "*",
"guzzlehttp/guzzle": "^7.7",
"intervention/image": "2.*",
"dedoc/scramble": "^0.9.0",
"inertiajs/inertia-laravel": "^1.0.0",
"laravel/framework": "^10.13.0",
Expand Down
Loading

0 comments on commit 8ed2d61

Please sign in to comment.