diff --git a/.env.example b/.env.example
index a843a46f..c9162340 100644
--- a/.env.example
+++ b/.env.example
@@ -62,3 +62,5 @@ GOOGLE_REDIRECT_URI=http://escooters.blumilk.localhost/login/google/redirect
FACEBOOK_CLIENT_ID=
FACEBOOK_CLIENT_SECRET=
FACEBOOK_REDIRECT_URI=http://escooters.blumilk.localhost/login/facebook/redirect
+
+OPENAI_API_KEY=
diff --git a/app/Exceptions/ExceptionHandler.php b/app/Exceptions/ExceptionHandler.php
index f029365c..ae4f1735 100644
--- a/app/Exceptions/ExceptionHandler.php
+++ b/app/Exceptions/ExceptionHandler.php
@@ -40,6 +40,10 @@ public function render($request, Throwable $exception)
return back()->withErrors($exception->errors());
}
+ if ($exception instanceof OpenAiException) {
+ return response()->json(["error" => $exception->getMessage()], $exception->getCode());
+ }
+
$response = parent::render($request, $exception);
$statusCode = $response->status();
diff --git a/app/Exceptions/OpenAiException.php b/app/Exceptions/OpenAiException.php
new file mode 100644
index 00000000..cc20998b
--- /dev/null
+++ b/app/Exceptions/OpenAiException.php
@@ -0,0 +1,14 @@
+where("city_id", $city->id)
+ ->first();
+
+ if (!$rules || $rules->rules_en === null || $rules->rules_pl === null) {
+ $cityData = [
+ "cityId" => $city->id,
+ "countryId" => $country->id,
+ "cityName" => $city->name,
+ "countryName" => $country->name,
+ ];
+ $importer = new OpenAIService();
+ $data = $importer->importRulesForCity($cityData, true);
+ } else {
+ $data = [
+ "country" => $country->name,
+ "city" => $city->name,
+ "rules_en" => $rules->rules_en,
+ "rules_pl" => $rules->rules_pl,
+ ];
+ }
+
+ return response()->json($data);
+ }
+
+ public function importRules(Request $request, OpenAIService $importer): JsonResponse
+ {
+ $force = $request->input("force", false);
+ $importer->importRulesForAllCities($force);
+
+ return response()->json(["message" => "Rules import started"]);
+ }
+}
diff --git a/app/Http/Controllers/RulesController.php b/app/Http/Controllers/RulesController.php
new file mode 100644
index 00000000..c3b822d5
--- /dev/null
+++ b/app/Http/Controllers/RulesController.php
@@ -0,0 +1,47 @@
+where("city_id", $city->id)
+ ->first();
+
+ if (!$rules || $rules->rules_en === null || $rules->rules_pl === null) {
+ $cityData = [
+ "cityId" => $city->id,
+ "countryId" => $country->id,
+ "cityName" => $city->name,
+ "countryName" => $country->name,
+ ];
+ $importer = new OpenAIService();
+ $data = $importer->importRulesForCity($cityData, true);
+ } else {
+ $data = [
+ "country" => $country->name,
+ "city" => $city->name,
+ "rules_en" => $rules->rules_en,
+ "rules_pl" => $rules->rules_pl,
+ ];
+ }
+
+ return $data;
+ }
+
+ public function importRules(Request $request, OpenAIService $importer): void
+ {
+ $force = $request->input("force", false);
+ $importer->importRulesForAllCities($force);
+ }
+}
diff --git a/app/Jobs/ImportCityRulesJob.php b/app/Jobs/ImportCityRulesJob.php
new file mode 100644
index 00000000..6c6458e8
--- /dev/null
+++ b/app/Jobs/ImportCityRulesJob.php
@@ -0,0 +1,30 @@
+importRulesForCity($this->cityData, $this->force);
+ }
+}
diff --git a/app/Models/Rules.php b/app/Models/Rules.php
new file mode 100644
index 00000000..60170c95
--- /dev/null
+++ b/app/Models/Rules.php
@@ -0,0 +1,29 @@
+belongsTo(City::class);
+ }
+
+ public function country(): BelongsTo
+ {
+ return $this->belongsTo(Country::class);
+ }
+}
diff --git a/app/Services/OpenAIService.php b/app/Services/OpenAIService.php
new file mode 100644
index 00000000..41765778
--- /dev/null
+++ b/app/Services/OpenAIService.php
@@ -0,0 +1,130 @@
+client = OpenAI::client(config("openai.token"));
+ } catch (Throwable $e) {
+ throw new OpenAiException();
+ }
+
+ $this->countriesKnownToHaveUniformRules = Storage::disk("public")->json("citiesKnownToHaveUniformRules.json");
+ }
+
+ public function importRulesForAllCities(bool $force): void
+ {
+ $cities = City::query()->whereHas("cityProviders")->orderBy("country_id")->get();
+
+ $importInfo = ImportInfo::query()->create([
+ "who_runs_it" => "admin",
+ "status" => "running",
+ ]);
+ $jobs = [];
+
+ foreach ($cities as $city) {
+ $cityData = [
+ "cityId" => $city->id,
+ "countryId" => $city->country_id,
+ "cityName" => $city->name,
+ "countryName" => $city->country->name,
+ ];
+ $jobs[] = new ImportCityRulesJob($cityData, $force);
+ ImportCityRulesJob::dispatch($cityData, $force);
+ }
+
+ Bus::batch($jobs)
+ ->catch(fn() => ImportInfoDetail::query()->updateOrCreate([
+ "import_info_id" => $importInfo->id,
+ "provider_name" => "OpenAI",
+ "code" => 400,
+ ]))
+ ->finally(fn() => ImportInfo::query()->where("id", $importInfo->id)->update([
+ "status" => "finished",
+ ]))
+ ->dispatch();
+ }
+
+ public function importRulesForCity(array $cityData, bool $force): array
+ {
+ $cityId = $cityData["cityId"];
+ $countryId = $cityData["countryId"];
+ $cityName = $cityData["cityName"];
+ $countryName = $cityData["countryName"];
+
+ $promptEn = "Act as a helpful assistant. Explain what are the legal limitations for riding electric scooters in $cityName, $countryName? Contain information about: max speed, helmet requirements, allowed ABV, passengers, other relevant details. Be formal, speak English. Don't include city name in your response. If you don't have information answering the question, write 'null'.";
+ $promptPl = "Translate to polish: ";
+ $currentRulesInCountry = Rules::query()->where("country_id", $countryId)->first();
+
+ if ($this->checkIfRulesExist($countryName, $currentRulesInCountry) && !$force) {
+ $rulesEn = $currentRulesInCountry->rules_en;
+ $rulesPl = $currentRulesInCountry->rules_pl;
+ } else {
+ $rulesEn = $this->askGPT($promptEn);
+ $rulesPl = $this->askGPT($promptPl . $rulesEn);
+ }
+
+ if (strlen($rulesEn) < 700 || strlen($rulesPl) < 700) {
+ return [
+ "city" => $cityName,
+ "country" => $countryName,
+ "rules_en" => null,
+ "rules_pl" => null,
+ ];
+ }
+
+ Rules::query()->updateOrCreate([
+ "city_id" => $cityId,
+ "country_id" => $countryId,
+ "rules_en" => $rulesEn,
+ "rules_pl" => $rulesPl,
+ ]);
+
+ return [
+ "city" => $cityName,
+ "country" => $countryName,
+ "rules_en" => $rulesEn,
+ "rules_pl" => $rulesPl,
+ ];
+ }
+
+ private function askGPT(string $prompt): string
+ {
+ $response = $this->client->chat()->create([
+ "model" => "gpt-3.5-turbo",
+ "messages" => [
+ [
+ "role" => "user",
+ "content" => $prompt,
+ ],
+ ],
+ ]);
+
+ return $response["choices"][0]["message"]["content"];
+ }
+
+ private function checkIfRulesExist(string $countryName, ?Rules $currentRulesInCountry): bool
+ {
+ return in_array($countryName, $this->countriesKnownToHaveUniformRules, true) && $currentRulesInCountry !== null && $currentRulesInCountry->rules_en !== null && $currentRulesInCountry->rules_pl !== null;
+ }
+}
diff --git a/composer.json b/composer.json
index 7d390c51..1bd1e16e 100644
--- a/composer.json
+++ b/composer.json
@@ -10,13 +10,15 @@
"ext-gd": "*",
"ext-pdo": "*",
"guzzlehttp/guzzle": "^7.7",
- "inertiajs/inertia-laravel": "^0.6.9",
+ "dedoc/scramble": "^0.9.0",
+ "inertiajs/inertia-laravel": "^1.0.0",
"intervention/image": "2.*",
"laravel/framework": "^10.13.0",
"laravel/sanctum": "^3.2.5",
"laravel/socialite": "^5.10",
"laravel/tinker": "^2.8.1",
+ "openai-php/client": "^0.8.1",
"sentry/sentry-laravel": "^3.7",
"spatie/laravel-permission": "^6.1",
"stichoza/google-translate-php": "^5.1",
diff --git a/composer.lock b/composer.lock
index 28c5e7f0..dd92c706 100644
--- a/composer.lock
+++ b/composer.lock
@@ -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": "b855f256a634ad0b33f1370b2d34f81d",
+ "content-hash": "4cc2044218af93556d4655e969efa960",
"packages": [
{
"name": "brick/math",
@@ -201,6 +201,80 @@
],
"time": "2023-12-20T15:40:13+00:00"
},
+ {
+ "name": "dedoc/scramble",
+ "version": "v0.9.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/dedoc/scramble.git",
+ "reference": "6280da6809eecaa03243d726b957cc174b1ccb70"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/dedoc/scramble/zipball/6280da6809eecaa03243d726b957cc174b1ccb70",
+ "reference": "6280da6809eecaa03243d726b957cc174b1ccb70",
+ "shasum": ""
+ },
+ "require": {
+ "illuminate/contracts": "^10.0|^11.0",
+ "nikic/php-parser": "^5.0",
+ "php": "^8.1",
+ "phpstan/phpdoc-parser": "^1.0",
+ "spatie/laravel-package-tools": "^1.9.2"
+ },
+ "require-dev": {
+ "laravel/pint": "^v1.1.0",
+ "nunomaduro/collision": "^7.0|^8.0",
+ "orchestra/testbench": "^8.0|^9.0",
+ "pestphp/pest": "^2.34",
+ "pestphp/pest-plugin-laravel": "^2.3",
+ "phpunit/phpunit": "^10.5",
+ "spatie/pest-plugin-snapshots": "^2.1"
+ },
+ "type": "library",
+ "extra": {
+ "laravel": {
+ "providers": [
+ "Dedoc\\Scramble\\ScrambleServiceProvider"
+ ]
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Dedoc\\Scramble\\": "src",
+ "Dedoc\\Scramble\\Database\\Factories\\": "database/factories"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Roman Lytvynenko",
+ "email": "litvinenko95@gmail.com",
+ "role": "Developer"
+ }
+ ],
+ "description": "Automatic generation of API documentation for Laravel applications.",
+ "homepage": "https://github.com/dedoc/scramble",
+ "keywords": [
+ "documentation",
+ "laravel",
+ "openapi"
+ ],
+ "support": {
+ "issues": "https://github.com/dedoc/scramble/issues",
+ "source": "https://github.com/dedoc/scramble/tree/v0.9.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/romalytvynenko",
+ "type": "github"
+ }
+ ],
+ "time": "2024-03-11T19:27:28+00:00"
+ },
{
"name": "dflydev/dot-access-data",
"version": "v3.0.2",
@@ -1239,27 +1313,27 @@
},
{
"name": "inertiajs/inertia-laravel",
- "version": "v0.6.11",
+ "version": "v1.0.0",
"source": {
"type": "git",
"url": "https://github.com/inertiajs/inertia-laravel.git",
- "reference": "2a1e19048f95c0e4adb2b2733f9119e49c4fc09f"
+ "reference": "fcf3d6db1a259a55d8d18cf43fc971202c1f6b0d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/inertiajs/inertia-laravel/zipball/2a1e19048f95c0e4adb2b2733f9119e49c4fc09f",
- "reference": "2a1e19048f95c0e4adb2b2733f9119e49c4fc09f",
+ "url": "https://api.github.com/repos/inertiajs/inertia-laravel/zipball/fcf3d6db1a259a55d8d18cf43fc971202c1f6b0d",
+ "reference": "fcf3d6db1a259a55d8d18cf43fc971202c1f6b0d",
"shasum": ""
},
"require": {
"ext-json": "*",
- "laravel/framework": "^6.0|^7.0|^8.74|^9.0|^10.0",
- "php": "^7.2|~8.0.0|~8.1.0|~8.2.0|~8.3.0"
+ "laravel/framework": "^8.74|^9.0|^10.0|^11.0",
+ "php": "^7.3|~8.0.0|~8.1.0|~8.2.0|~8.3.0"
},
"require-dev": {
"mockery/mockery": "^1.3.3",
- "orchestra/testbench": "^4.0|^5.0|^6.4|^7.0|^8.0",
- "phpunit/phpunit": "^8.0|^9.5.8",
+ "orchestra/testbench": "^6.4|^7.0|^8.0|^9.0",
+ "phpunit/phpunit": "^8.0|^9.5.8|^10.4",
"roave/security-advisories": "dev-master"
},
"suggest": {
@@ -1271,6 +1345,9 @@
"providers": [
"Inertia\\ServiceProvider"
]
+ },
+ "branch-alias": {
+ "dev-master": "1.x-dev"
}
},
"autoload": {
@@ -1299,7 +1376,7 @@
],
"support": {
"issues": "https://github.com/inertiajs/inertia-laravel/issues",
- "source": "https://github.com/inertiajs/inertia-laravel/tree/v0.6.11"
+ "source": "https://github.com/inertiajs/inertia-laravel/tree/v1.0.0"
},
"funding": [
{
@@ -1307,7 +1384,7 @@
"type": "github"
}
],
- "time": "2023-10-27T10:59:02+00:00"
+ "time": "2024-03-09T00:30:58+00:00"
},
{
"name": "intervention/image",
@@ -1845,16 +1922,16 @@
},
{
"name": "laravel/socialite",
- "version": "v5.13.2",
+ "version": "v5.14.0",
"source": {
"type": "git",
"url": "https://github.com/laravel/socialite.git",
- "reference": "278d4615f68205722b3a129135774b3764b28a90"
+ "reference": "c7b0193a3753a29aff8ce80aa2f511917e6ed68a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/socialite/zipball/278d4615f68205722b3a129135774b3764b28a90",
- "reference": "278d4615f68205722b3a129135774b3764b28a90",
+ "url": "https://api.github.com/repos/laravel/socialite/zipball/c7b0193a3753a29aff8ce80aa2f511917e6ed68a",
+ "reference": "c7b0193a3753a29aff8ce80aa2f511917e6ed68a",
"shasum": ""
},
"require": {
@@ -1913,7 +1990,7 @@
"issues": "https://github.com/laravel/socialite/issues",
"source": "https://github.com/laravel/socialite"
},
- "time": "2024-04-26T13:48:16+00:00"
+ "time": "2024-05-03T20:31:38+00:00"
},
{
"name": "laravel/tinker",
@@ -3095,6 +3172,98 @@
],
"time": "2023-11-13T09:31:12+00:00"
},
+ {
+ "name": "openai-php/client",
+ "version": "v0.8.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/openai-php/client.git",
+ "reference": "0f755fafa4d3f8d5c8ed964d3166d078fac0605a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/openai-php/client/zipball/0f755fafa4d3f8d5c8ed964d3166d078fac0605a",
+ "reference": "0f755fafa4d3f8d5c8ed964d3166d078fac0605a",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^8.1.0",
+ "php-http/discovery": "^1.19.4",
+ "php-http/multipart-stream-builder": "^1.3.0",
+ "psr/http-client": "^1.0.3",
+ "psr/http-client-implementation": "^1.0.1",
+ "psr/http-factory-implementation": "*",
+ "psr/http-message": "^1.1.0|^2.0.0"
+ },
+ "require-dev": {
+ "guzzlehttp/guzzle": "^7.8.1",
+ "guzzlehttp/psr7": "^2.6.2",
+ "laravel/pint": "^1.15.0",
+ "mockery/mockery": "^1.6.11",
+ "nunomaduro/collision": "^7.10.0",
+ "pestphp/pest": "^2.34.6",
+ "pestphp/pest-plugin-arch": "^2.7",
+ "pestphp/pest-plugin-type-coverage": "^2.8.1",
+ "phpstan/phpstan": "^1.10.66",
+ "rector/rector": "^1.0.4",
+ "symfony/var-dumper": "^6.4.4"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "src/OpenAI.php"
+ ],
+ "psr-4": {
+ "OpenAI\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nuno Maduro",
+ "email": "enunomaduro@gmail.com"
+ },
+ {
+ "name": "Sandro Gehri"
+ }
+ ],
+ "description": "OpenAI PHP is a supercharged PHP API client that allows you to interact with the Open AI API",
+ "keywords": [
+ "GPT-3",
+ "api",
+ "client",
+ "codex",
+ "dall-e",
+ "language",
+ "natural",
+ "openai",
+ "php",
+ "processing",
+ "sdk"
+ ],
+ "support": {
+ "issues": "https://github.com/openai-php/client/issues",
+ "source": "https://github.com/openai-php/client/tree/v0.8.5"
+ },
+ "funding": [
+ {
+ "url": "https://www.paypal.com/paypalme/enunomaduro",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/gehrisandro",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/nunomaduro",
+ "type": "github"
+ }
+ ],
+ "time": "2024-04-15T19:11:23+00:00"
+ },
{
"name": "paragonie/constant_time_encoding",
"version": "v2.6.3",
@@ -3541,6 +3710,62 @@
"abandoned": "psr/http-factory",
"time": "2023-04-14T14:16:17+00:00"
},
+ {
+ "name": "php-http/multipart-stream-builder",
+ "version": "1.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-http/multipart-stream-builder.git",
+ "reference": "f5938fd135d9fa442cc297dc98481805acfe2b6a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-http/multipart-stream-builder/zipball/f5938fd135d9fa442cc297dc98481805acfe2b6a",
+ "reference": "f5938fd135d9fa442cc297dc98481805acfe2b6a",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1 || ^8.0",
+ "php-http/discovery": "^1.15",
+ "psr/http-factory-implementation": "^1.0"
+ },
+ "require-dev": {
+ "nyholm/psr7": "^1.0",
+ "php-http/message": "^1.5",
+ "php-http/message-factory": "^1.0.2",
+ "phpunit/phpunit": "^7.5.15 || ^8.5 || ^9.3"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Http\\Message\\MultipartStream\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Tobias Nyholm",
+ "email": "tobias.nyholm@gmail.com"
+ }
+ ],
+ "description": "A builder class that help you create a multipart stream",
+ "homepage": "http://php-http.org",
+ "keywords": [
+ "factory",
+ "http",
+ "message",
+ "multipart stream",
+ "stream"
+ ],
+ "support": {
+ "issues": "https://github.com/php-http/multipart-stream-builder/issues",
+ "source": "https://github.com/php-http/multipart-stream-builder/tree/1.3.0"
+ },
+ "time": "2023-04-28T14:10:22+00:00"
+ },
{
"name": "php-http/promise",
"version": "1.3.1",
@@ -3778,6 +4003,53 @@
],
"time": "2024-03-03T02:14:58+00:00"
},
+ {
+ "name": "phpstan/phpdoc-parser",
+ "version": "1.29.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpstan/phpdoc-parser.git",
+ "reference": "536889f2b340489d328f5ffb7b02bb6b183ddedc"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/536889f2b340489d328f5ffb7b02bb6b183ddedc",
+ "reference": "536889f2b340489d328f5ffb7b02bb6b183ddedc",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2 || ^8.0"
+ },
+ "require-dev": {
+ "doctrine/annotations": "^2.0",
+ "nikic/php-parser": "^4.15",
+ "php-parallel-lint/php-parallel-lint": "^1.2",
+ "phpstan/extension-installer": "^1.0",
+ "phpstan/phpstan": "^1.5",
+ "phpstan/phpstan-phpunit": "^1.1",
+ "phpstan/phpstan-strict-rules": "^1.0",
+ "phpunit/phpunit": "^9.5",
+ "symfony/process": "^5.2"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "PHPStan\\PhpDocParser\\": [
+ "src/"
+ ]
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "PHPDoc parser with support for nullable, intersection and generic types",
+ "support": {
+ "issues": "https://github.com/phpstan/phpdoc-parser/issues",
+ "source": "https://github.com/phpstan/phpdoc-parser/tree/1.29.0"
+ },
+ "time": "2024-05-06T12:04:23+00:00"
+ },
{
"name": "psr/clock",
"version": "1.0.0",
@@ -4743,6 +5015,66 @@
],
"time": "2023-10-12T14:38:46+00:00"
},
+ {
+ "name": "spatie/laravel-package-tools",
+ "version": "1.16.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/spatie/laravel-package-tools.git",
+ "reference": "ddf678e78d7f8b17e5cdd99c0c3413a4a6592e53"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/ddf678e78d7f8b17e5cdd99c0c3413a4a6592e53",
+ "reference": "ddf678e78d7f8b17e5cdd99c0c3413a4a6592e53",
+ "shasum": ""
+ },
+ "require": {
+ "illuminate/contracts": "^9.28|^10.0|^11.0",
+ "php": "^8.0"
+ },
+ "require-dev": {
+ "mockery/mockery": "^1.5",
+ "orchestra/testbench": "^7.7|^8.0",
+ "pestphp/pest": "^1.22",
+ "phpunit/phpunit": "^9.5.24",
+ "spatie/pest-plugin-test-time": "^1.1"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Spatie\\LaravelPackageTools\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Freek Van der Herten",
+ "email": "freek@spatie.be",
+ "role": "Developer"
+ }
+ ],
+ "description": "Tools for creating Laravel packages",
+ "homepage": "https://github.com/spatie/laravel-package-tools",
+ "keywords": [
+ "laravel-package-tools",
+ "spatie"
+ ],
+ "support": {
+ "issues": "https://github.com/spatie/laravel-package-tools/issues",
+ "source": "https://github.com/spatie/laravel-package-tools/tree/1.16.4"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/spatie",
+ "type": "github"
+ }
+ ],
+ "time": "2024-03-20T07:29:11+00:00"
+ },
{
"name": "spatie/laravel-permission",
"version": "6.7.0",
@@ -8006,16 +8338,16 @@
},
{
"name": "composer/xdebug-handler",
- "version": "3.0.4",
+ "version": "3.0.5",
"source": {
"type": "git",
"url": "https://github.com/composer/xdebug-handler.git",
- "reference": "4f988f8fdf580d53bdb2d1278fe93d1ed5462255"
+ "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/4f988f8fdf580d53bdb2d1278fe93d1ed5462255",
- "reference": "4f988f8fdf580d53bdb2d1278fe93d1ed5462255",
+ "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6c1925561632e83d60a44492e0b344cf48ab85ef",
+ "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef",
"shasum": ""
},
"require": {
@@ -8052,7 +8384,7 @@
"support": {
"irc": "ircs://irc.libera.chat:6697/composer",
"issues": "https://github.com/composer/xdebug-handler/issues",
- "source": "https://github.com/composer/xdebug-handler/tree/3.0.4"
+ "source": "https://github.com/composer/xdebug-handler/tree/3.0.5"
},
"funding": [
{
@@ -8068,7 +8400,7 @@
"type": "tidelift"
}
],
- "time": "2024-03-26T18:29:49+00:00"
+ "time": "2024-05-06T16:37:16+00:00"
},
{
"name": "fakerphp/faker",
@@ -8206,16 +8538,16 @@
},
{
"name": "friendsofphp/php-cs-fixer",
- "version": "v3.54.0",
+ "version": "v3.56.0",
"source": {
"type": "git",
"url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git",
- "reference": "2aecbc8640d7906c38777b3dcab6f4ca79004d08"
+ "reference": "4429303e62a4ce583ddfe64ff5c34c76bcf74931"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/2aecbc8640d7906c38777b3dcab6f4ca79004d08",
- "reference": "2aecbc8640d7906c38777b3dcab6f4ca79004d08",
+ "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/4429303e62a4ce583ddfe64ff5c34c76bcf74931",
+ "reference": "4429303e62a4ce583ddfe64ff5c34c76bcf74931",
"shasum": ""
},
"require": {
@@ -8287,7 +8619,7 @@
],
"support": {
"issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues",
- "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.54.0"
+ "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.56.0"
},
"funding": [
{
@@ -8295,7 +8627,7 @@
"type": "github"
}
],
- "time": "2024-04-17T08:12:13+00:00"
+ "time": "2024-05-07T15:50:05+00:00"
},
{
"name": "hamcrest/hamcrest-php",
diff --git a/config/mapbox.php b/config/mapbox.php
index 18a50d08..24b24359 100644
--- a/config/mapbox.php
+++ b/config/mapbox.php
@@ -4,6 +4,5 @@
return [
"token" => env("MAPBOX_TOKEN"),
-
"api_url" => env("MAPBOX_URL"),
];
diff --git a/config/openai.php b/config/openai.php
new file mode 100644
index 00000000..aee31f8b
--- /dev/null
+++ b/config/openai.php
@@ -0,0 +1,7 @@
+ env("OPENAI_API_KEY"),
+];
diff --git a/database/migrations/2024_01_07_182205_create_rules_for_cities_table.php b/database/migrations/2024_01_07_182205_create_rules_for_cities_table.php
new file mode 100644
index 00000000..90232907
--- /dev/null
+++ b/database/migrations/2024_01_07_182205_create_rules_for_cities_table.php
@@ -0,0 +1,34 @@
+id();
+ $table->timestamps();
+ $table->unsignedBigInteger("city_id");
+ $table->foreign("city_id")
+ ->references("id")
+ ->on("cities")
+ ->onDelete("cascade");
+ $table->unsignedBigInteger("country_id");
+ $table->foreign("country_id")
+ ->references("id")
+ ->on("countries")
+ ->onDelete("cascade");
+ $table->text("rules_en")->nullable();
+ $table->text("rules_pl")->nullable();
+ });
+ }
+
+ public function down(): void
+ {
+ Schema::dropIfExists("rules_for_cities");
+ }
+};
diff --git a/database/seeders/ProviderSeeder.php b/database/seeders/ProviderSeeder.php
index de08d7a5..a30efa2f 100644
--- a/database/seeders/ProviderSeeder.php
+++ b/database/seeders/ProviderSeeder.php
@@ -61,6 +61,7 @@ public function run(): void
["name" => VeoDataImporter::getProviderName(), "color" => "#000000", "url" => "https://www.veoride.com/", "android_url" => "https://play.google.com/store/apps/details?id=com.pgt.veoride", "ios_url" => "https://apps.apple.com/us/app/veoride/id1279820696"],
["name" => WindDataImporter::getProviderName(), "color" => "#fffa00", "url" => "https://www.wind.co/", "android_url" => "https://play.google.com/store/apps/details?id=com.zen.zbike&referrer=utm_source%3Dofficialweb%26utm_medium%3Dother%26utm_term%3Dglobal", "ios_url" => "https://apps.apple.com/cn/app/yango-wind-e-scooter-sharing/id1247826304?utm_source=officialweb&utm_medium=other&time=1703025387273&utm_term=global"],
["name" => WheeMoveDataImporter::getProviderName(), "color" => "#31682d", "url" => "https://www.wheemove.com/", "android_url" => "https://play.google.com/store/apps/details?id=com.whee.android", "ios_url" => "https://apps.apple.com/us/app/whee-e-scooter-sharing/id1465111214?l=es&ls=1"],
+ ["name" => "OpenAI", "color" => "#ffffff", "url" => "https://openai.com"],
];
foreach ($providers as $provider) {
diff --git a/lang/pl.json b/lang/pl.json
index 644185ed..ee232706 100644
--- a/lang/pl.json
+++ b/lang/pl.json
@@ -168,6 +168,13 @@
"Login failed.": "Logowanie nie powiodło się.",
"Please, rate that city": "Oceń miasto",
"You can also login by:": "Możesz również zalogować się przez:",
+ "Rules import started.": "Import zasad uruchomiony.",
+ "Run rules import": "Uruchom import zasad",
+ "Found": "Znaleziono",
+ "loading info about rules, please wait": "ładowanie informacji o zasadach, proszę czekać",
+ "There was an error fetching rules.": "Wystąpił błąd podczas pobierania zasad.",
+ "Results found": "Znaleziono wyników",
+ "City not found.": "Miasto nieznalezione.",
"Change in favorite city": "Zmiana w ulubionym mieście",
"Hello!": "Witaj!",
"There has been a change in your favorite city.": "Nastąpiła zmiana w Twoim ulubionym mieście.",
diff --git a/public/providers/openai.png b/public/providers/openai.png
new file mode 100644
index 00000000..18310efb
Binary files /dev/null and b/public/providers/openai.png differ
diff --git a/resources/css/app.css b/resources/css/app.css
index 9430c6ad..2cb25885 100644
--- a/resources/css/app.css
+++ b/resources/css/app.css
@@ -25,6 +25,25 @@
.scrollbar::-webkit-scrollbar-thumb:hover {
background: #d3d3d3;
}
+
+ div.regulations>div:nth-child(2) {
+ max-height: 0vh;
+ margin-bottom: 0px;
+ transition: all 0.5s ease-in-out;
+ }
+
+ div.regulations>div:nth-child(2).show {
+ max-height: 50vh;
+ margin-bottom: 12px;
+ }
+
+ div.regulations>div>svg {
+ transform: rotate(0deg);
+ }
+
+ div.regulations>div>svg.rotated {
+ transform: rotate(-180deg);
+ }
.provider-buttons {
position: absolute;
transition: all 0.15s ease-in-out;
@@ -77,5 +96,6 @@
}
.provider-buttons>a{
transition: all 0.15s ease-in-out;
+
}
}
diff --git a/resources/js/Pages/City/Index.vue b/resources/js/Pages/City/Index.vue
index 0bd59b1a..69e51bfa 100644
--- a/resources/js/Pages/City/Index.vue
+++ b/resources/js/Pages/City/Index.vue
@@ -2,8 +2,8 @@
import Nav from '@/Shared/Layout/Nav.vue'
import Map from '@/Shared/Layout/Map.vue'
import { breakpointsTailwind, useBreakpoints } from '@vueuse/core'
-import { computed, onUnmounted, ref } from 'vue'
-import { MapIcon, XMarkIcon, StarIcon, PaperAirplaneIcon } from '@heroicons/vue/24/outline'
+import { computed, onUnmounted, ref, reactive } from 'vue'
+import { MapIcon, XMarkIcon, StarIcon, PaperAirplaneIcon, ArrowDownIcon } from '@heroicons/vue/24/outline'
import { useFilterStore } from '@/Shared/Stores/FilterStore'
import FavoriteButton from '@/Shared/Components/FavoriteButton.vue'
import ProviderIcons from '@/Shared/Components/ProviderIcons.vue'
@@ -14,16 +14,24 @@ import { useToast } from 'vue-toastification'
import Pagination from '@/Shared/Components/Pagination.vue'
import InfoPopup from '@/Shared/Components/InfoPopup.vue'
import Opinion from '@/Shared/Components/Opinion.vue'
+import axios from 'axios'
+
const toast = useToast()
const page = usePage()
const isAuth = computed(() => page.props.auth.isAuth)
+const regulationsOpen = ref(false)
+const rules = reactive({ pl: 'ładowanie informacji o zasadach, proszę czekać...', en: 'loading info about rules, please wait...' })
const props = defineProps({
city: Object,
providers: Object,
cityOpinions: Object,
})
+fetchRegulations()
+
+const currentLocale = ref(computed(() => page.props.locale))
+const currentRules = ref(computed(()=>rules[currentLocale.value]))
const breakpoints = useBreakpoints(breakpointsTailwind)
const isMobile = ref(breakpoints.smaller('lg'))
@@ -57,6 +65,22 @@ function setRating(starIndex) {
opinionForm.rating = starIndex
}
+function toggleRegulations() {
+ regulationsOpen.value = !regulationsOpen.value
+
+}
+
+function fetchRegulations() {
+ axios.get(`/api/rules/${props.city.country.name.toLowerCase()}/${props.city.name.toLowerCase()}`)
+ .then(response => {
+ rules.pl= response.data.rules_pl
+ rules.en = response.data.rules_en
+ })
+ .catch(() => {
+ toast.error(__('There was an error fetching rules.'))
+ })
+}
+
const emptyRatingError = ref('')
function createOpinion() {
@@ -75,6 +99,7 @@ function createOpinion() {
},
})
}
+
}
@@ -106,7 +131,14 @@ function createOpinion() {
{{ city.latitude }}, {{ city.longitude }}