feat: get weather from weatherapi (#5668)
This commit is contained in:
+2
-3
@@ -166,10 +166,9 @@ LOCATION_IQ_API_KEY=
|
||||
ENABLE_WEATHER=false
|
||||
|
||||
# Access to weather data from darksky api
|
||||
# https://darksky.net/dev/register
|
||||
# Darksky provides an api with 1000 free API calls per day
|
||||
# https://www.weatherapi.com/signup.aspx
|
||||
# You need to enable the weather above if you provide an API key here.
|
||||
DARKSKY_API_KEY=
|
||||
WEATHERAPI_KEY=
|
||||
|
||||
# Configure rate limits for RouteService per minute
|
||||
RATE_LIMIT_PER_MINUTE_API=60
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class NoCoordinatesException extends RuntimeException
|
||||
{
|
||||
}
|
||||
@@ -2,9 +2,11 @@
|
||||
|
||||
namespace App\Helpers;
|
||||
|
||||
use App\Jobs\GetGPSCoordinate;
|
||||
use App\Models\Account\Weather;
|
||||
use App\Models\Contact\Address;
|
||||
use App\Services\Instance\Weather\GetWeatherInformation;
|
||||
use App\Jobs\GetWeatherInformation;
|
||||
use Illuminate\Support\Facades\Bus;
|
||||
|
||||
class WeatherHelper
|
||||
{
|
||||
@@ -20,16 +22,14 @@ class WeatherHelper
|
||||
return null;
|
||||
}
|
||||
|
||||
$weather = $address->place->weathers()->orderBy('created_at', 'desc')->first();
|
||||
$weather = $address->place->weathers()
|
||||
->orderBy('created_at', 'desc')
|
||||
->first();
|
||||
|
||||
// only get weather data if weather is either not existant or if is
|
||||
// more than 6h old
|
||||
if (is_null($weather)) {
|
||||
$weather = self::callWeatherAPI($address);
|
||||
} else {
|
||||
if (! $weather->created_at->between(now()->subHours(6), now())) {
|
||||
$weather = self::callWeatherAPI($address);
|
||||
}
|
||||
if (is_null($weather) || ! $weather->created_at->between(now()->subHours(6), now())) {
|
||||
self::callWeatherAPI($address);
|
||||
}
|
||||
|
||||
return $weather;
|
||||
@@ -39,16 +39,21 @@ class WeatherHelper
|
||||
* Make the call to the weather service.
|
||||
*
|
||||
* @param Address $address
|
||||
* @return Weather|null
|
||||
*/
|
||||
private static function callWeatherAPI(Address $address): ?Weather
|
||||
private static function callWeatherAPI(Address $address): void
|
||||
{
|
||||
try {
|
||||
return app(GetWeatherInformation::class)->execute([
|
||||
'place_id' => $address->place->id,
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return null;
|
||||
$jobs = [];
|
||||
|
||||
if (is_null($address->place->latitude)
|
||||
&& config('monica.enable_geolocation') && ! is_null(config('monica.location_iq_api_key'))) {
|
||||
$jobs[] = new GetGPSCoordinate($address->place);
|
||||
}
|
||||
|
||||
if (config('monica.enable_weather') && ! is_null(config('monica.weatherapi_key'))) {
|
||||
$jobs[] = new GetWeatherInformation($address->place);
|
||||
}
|
||||
|
||||
Bus::batch($jobs)
|
||||
->dispatch();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Models\Account\Place;
|
||||
use Illuminate\Bus\Batchable;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
@@ -14,7 +15,7 @@ use App\Services\Instance\Geolocalization\GetGPSCoordinate as GetGPSCoordinateSe
|
||||
|
||||
class GetGPSCoordinate implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
use Batchable, Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
/**
|
||||
* @var Place
|
||||
@@ -64,6 +65,10 @@ class GetGPSCoordinate implements ShouldQueue
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
if (($batch = $this->batch()) !== null && $batch->cancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
app(GetGPSCoordinateService::class)->execute([
|
||||
'account_id' => $this->place->account_id,
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Models\Account\Place;
|
||||
use Illuminate\Bus\Batchable;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use App\Exceptions\NoCoordinatesException;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use App\Services\Instance\Weather\GetWeatherInformation as GetWeatherInformationService;
|
||||
|
||||
class GetWeatherInformation implements ShouldQueue
|
||||
{
|
||||
use Batchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
/**
|
||||
* @var Place
|
||||
*/
|
||||
protected $place;
|
||||
|
||||
/**
|
||||
* The number of times the job may be attempted.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $tries = 10;
|
||||
|
||||
/**
|
||||
* The maximum number of unhandled exceptions to allow before failing.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $maxExceptions = 1;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Place $place)
|
||||
{
|
||||
$this->place = $place->withoutRelations();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
if (! $this->batching()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_null($this->place->latitude)) {
|
||||
$this->fail(new NoCoordinatesException());
|
||||
} else {
|
||||
app(GetWeatherInformationService::class)->execute([
|
||||
'account_id' => $this->place->account_id,
|
||||
'place_id' => $this->place->id,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace App\Models\Account;
|
||||
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
@@ -16,6 +18,8 @@ class Weather extends Model
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'account_id',
|
||||
'place_id',
|
||||
'weather_json',
|
||||
];
|
||||
|
||||
@@ -55,50 +59,90 @@ class Weather extends Model
|
||||
return $this->belongsTo(Place::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the weather code.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getSummaryCodeAttribute()
|
||||
{
|
||||
$json = $this->weather_json;
|
||||
|
||||
// currently.icon: Darksky version
|
||||
if (! ($icon = Arr::get($json, 'currently.icon'))) {
|
||||
if (($text = Arr::get($json, 'current.condition.text')) === 'Partly cloudy') {
|
||||
$icon = ((bool) Arr::get($json, 'current.is_day')) ? 'partly-cloudy-day' : 'partly-cloudy-night';
|
||||
} else {
|
||||
$icon = Str::of($text)->lower()->replace(' ', '-');
|
||||
}
|
||||
}
|
||||
|
||||
return $icon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the weather summary.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getSummaryAttribute($value)
|
||||
public function getSummaryAttribute()
|
||||
{
|
||||
$json = $this->weather_json;
|
||||
|
||||
return $json['currently']['summary'];
|
||||
return trans('app.weather_'.$this->summary_code);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the weather summary icon.
|
||||
* Get the weather icon.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getSummaryIconAttribute($value)
|
||||
{
|
||||
$json = $this->weather_json;
|
||||
|
||||
return $json['currently']['icon'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the emoji representing the weather.
|
||||
*
|
||||
* @return string
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function getEmoji()
|
||||
public function getEmojiAttribute()
|
||||
{
|
||||
switch ($this->summary_icon) {
|
||||
switch ($this->summary_code) {
|
||||
case 'sunny':
|
||||
case 'clear-day':
|
||||
$string = '☀️';
|
||||
$string = '🌞';
|
||||
break;
|
||||
case 'clear':
|
||||
case 'clear-night':
|
||||
$string = '🌌';
|
||||
$string = '🌃';
|
||||
break;
|
||||
case 'light-drizzle':
|
||||
case 'patchy-light-drizzle':
|
||||
case 'patchy-light-rain':
|
||||
case 'light-rain':
|
||||
case 'moderate-rain-at-times':
|
||||
case 'moderate-rain':
|
||||
case 'patchy-rain-possible':
|
||||
case 'heavy-rain-at-times':
|
||||
case 'heavy-rain':
|
||||
case 'light-freezing-rain':
|
||||
case 'moderate-or-heavy-freezing-rain':
|
||||
case 'light-sleet':
|
||||
case 'moderate-or-heavy-rain-shower':
|
||||
case 'light-rain-shower':
|
||||
case 'torrential-rain-shower':
|
||||
case 'rain':
|
||||
$string = '🌧️';
|
||||
break;
|
||||
case 'snow':
|
||||
case 'blowing-snow':
|
||||
case 'patchy-light-snow':
|
||||
case 'light-snow':
|
||||
case 'patchy-moderate-snow':
|
||||
case 'moderate-snow':
|
||||
case 'patchy-heavy-snow':
|
||||
case 'heavy-snow':
|
||||
case 'light-snow-showers':
|
||||
case 'moderate-or-heavy-snow-showers':
|
||||
$string = '❄️';
|
||||
break;
|
||||
case 'patchy-snow-possible':
|
||||
case 'patchy-sleet-possible':
|
||||
case 'moderate-or-heavy-sleet':
|
||||
case 'light-sleet-showers':
|
||||
case 'moderate-or-heavy-sleet-showers':
|
||||
case 'sleet':
|
||||
$string = '🌨️';
|
||||
break;
|
||||
@@ -106,8 +150,12 @@ class Weather extends Model
|
||||
$string = '💨';
|
||||
break;
|
||||
case 'fog':
|
||||
case 'mist':
|
||||
case 'blizzard':
|
||||
case 'freezing-fog':
|
||||
$string = '🌫️';
|
||||
break;
|
||||
case 'overcast':
|
||||
case 'cloudy':
|
||||
$string = '☁️';
|
||||
break;
|
||||
@@ -117,6 +165,21 @@ class Weather extends Model
|
||||
case 'partly-cloudy-night':
|
||||
$string = '🎑';
|
||||
break;
|
||||
case 'freezing-drizzle':
|
||||
case 'heavy-freezing-drizzle':
|
||||
case 'patchy-freezing-drizzle-possible':
|
||||
case 'ice-pellets':
|
||||
case 'light-showers-of-ice-pellets':
|
||||
case 'moderate-or-heavy-showers-of-ice-pellets':
|
||||
$string = '🧊';
|
||||
break;
|
||||
case 'thundery-outbreaks-possible':
|
||||
case 'patchy-light-rain-with-thunder':
|
||||
case 'moderate-or-heavy-rain-with-thunder':
|
||||
case 'patchy-light-snow-with-thunder':
|
||||
case 'moderate-or-heavy-snow-with-thunder':
|
||||
$string = '⛈️';
|
||||
break;
|
||||
default:
|
||||
$string = '🌈';
|
||||
break;
|
||||
@@ -137,10 +200,10 @@ class Weather extends Model
|
||||
{
|
||||
$json = $this->weather_json;
|
||||
|
||||
$temperature = $json['currently']['temperature'];
|
||||
$temperature = Arr::get($json, 'currently.temperature') ?? Arr::get($json, 'current.temp_c');
|
||||
|
||||
if ($scale == 'fahrenheit') {
|
||||
$temperature = 9 / 5 * $temperature + 32;
|
||||
if ($scale === 'fahrenheit') {
|
||||
$temperature = Arr::get($json, 'current.temp_f', 9 / 5 * $temperature + 32);
|
||||
}
|
||||
|
||||
$temperature = round($temperature, 1);
|
||||
|
||||
@@ -1490,9 +1490,9 @@ class Contact extends Model
|
||||
* Get the weather information for this contact, based on the first address
|
||||
* on the profile.
|
||||
*
|
||||
* @return Weather
|
||||
* @return Weather|null
|
||||
*/
|
||||
public function getWeather()
|
||||
public function getWeather(): ?Weather
|
||||
{
|
||||
return WeatherHelper::getWeatherForAddress($this->addresses()->first());
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Http\Client\RequestException;
|
||||
use App\Exceptions\RateLimitedSecondException;
|
||||
use App\Exceptions\MissingEnvVariableException;
|
||||
|
||||
class GetGPSCoordinate extends BaseService
|
||||
{
|
||||
@@ -34,6 +35,8 @@ class GetGPSCoordinate extends BaseService
|
||||
*/
|
||||
public function execute(array $data)
|
||||
{
|
||||
$this->validateWeatherEnvVariables();
|
||||
|
||||
$this->validate($data);
|
||||
|
||||
$place = Place::where('account_id', $data['account_id'])
|
||||
@@ -42,18 +45,26 @@ class GetGPSCoordinate extends BaseService
|
||||
return $this->query($place);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure that geolocation env variables are set.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function validateWeatherEnvVariables()
|
||||
{
|
||||
if (! config('monica.enable_geolocation') || is_null(config('monica.location_iq_api_key'))) {
|
||||
throw new MissingEnvVariableException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the query to send with the API call.
|
||||
*
|
||||
* @param Place $place
|
||||
* @return string|null
|
||||
* @return string
|
||||
*/
|
||||
private function buildQuery(Place $place): ?string
|
||||
private function buildQuery(Place $place): string
|
||||
{
|
||||
if (! config('monica.enable_geolocation') || is_null(config('monica.location_iq_api_key'))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$query = http_build_query([
|
||||
'format' => 'json',
|
||||
'key' => config('monica.location_iq_api_key'),
|
||||
@@ -73,10 +84,6 @@ class GetGPSCoordinate extends BaseService
|
||||
{
|
||||
$query = $this->buildQuery($place);
|
||||
|
||||
if (is_null($query)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
$response = Http::get($query);
|
||||
$response->throw();
|
||||
|
||||
@@ -5,10 +5,10 @@ namespace App\Services\Instance\Weather;
|
||||
use Illuminate\Support\Str;
|
||||
use App\Models\Account\Place;
|
||||
use App\Services\BaseService;
|
||||
use App\Jobs\GetGPSCoordinate;
|
||||
use App\Models\Account\Weather;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use App\Exceptions\NoCoordinatesException;
|
||||
use App\Exceptions\MissingEnvVariableException;
|
||||
use Illuminate\Http\Client\HttpClientException;
|
||||
|
||||
@@ -22,6 +22,7 @@ class GetWeatherInformation extends BaseService
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'account_id' => 'required|integer|exists:accounts,id',
|
||||
'place_id' => 'required|integer|exists:places,id',
|
||||
];
|
||||
}
|
||||
@@ -42,17 +43,14 @@ class GetWeatherInformation extends BaseService
|
||||
|
||||
$this->validate($data);
|
||||
|
||||
$place = Place::findOrFail($data['place_id']);
|
||||
$place = Place::where('account_id', $data['account_id'])
|
||||
->findOrFail($data['place_id']);
|
||||
|
||||
if (is_null($place->latitude)) {
|
||||
$place = $this->fetchGPS($place);
|
||||
|
||||
if (is_null($place)) {
|
||||
return null;
|
||||
}
|
||||
throw new NoCoordinatesException();
|
||||
}
|
||||
|
||||
return $this->query($place);
|
||||
return $this->query($place, 'en');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -62,11 +60,7 @@ class GetWeatherInformation extends BaseService
|
||||
*/
|
||||
private function validateWeatherEnvVariables()
|
||||
{
|
||||
if (! config('monica.enable_weather')) {
|
||||
throw new MissingEnvVariableException();
|
||||
}
|
||||
|
||||
if (is_null(config('monica.darksky_api_key'))) {
|
||||
if (! config('monica.enable_weather') || is_null(config('monica.weatherapi_key'))) {
|
||||
throw new MissingEnvVariableException();
|
||||
}
|
||||
}
|
||||
@@ -79,24 +73,22 @@ class GetWeatherInformation extends BaseService
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function query(Place $place): ?Weather
|
||||
private function query(Place $place, ?string $lang = null): ?Weather
|
||||
{
|
||||
$query = $this->buildQuery($place);
|
||||
$query = $this->buildQuery($place, $lang);
|
||||
|
||||
try {
|
||||
$response = Http::get($query);
|
||||
$response->throw();
|
||||
|
||||
$weather = new Weather();
|
||||
$weather->weather_json = $response->object();
|
||||
$weather->account_id = $place->account_id;
|
||||
$weather->place_id = $place->id;
|
||||
$weather->save();
|
||||
|
||||
return $weather;
|
||||
return Weather::create([
|
||||
'account_id' => $place->account_id,
|
||||
'place_id' => $place->id,
|
||||
'weather_json' => $response->object(),
|
||||
]);
|
||||
} catch (HttpClientException $e) {
|
||||
Log::error(__CLASS__.' '.__FUNCTION__.': Error making the call: '.$e->getMessage(), [
|
||||
'query' => Str::of($query)->replace(config('monica.darksky_api_key'), '******'),
|
||||
'query' => Str::of($query)->replace(config('monica.weatherapi_key'), '******'),
|
||||
$e,
|
||||
]);
|
||||
}
|
||||
@@ -110,35 +102,19 @@ class GetWeatherInformation extends BaseService
|
||||
* @param Place $place
|
||||
* @return string
|
||||
*/
|
||||
private function buildQuery(Place $place)
|
||||
private function buildQuery(Place $place, ?string $lang = null)
|
||||
{
|
||||
$url = Str::finish(config('location.darksky_url'), '/');
|
||||
$key = config('monica.darksky_api_key');
|
||||
$coords = $place->latitude.','.$place->longitude;
|
||||
|
||||
$query = http_build_query([
|
||||
'exclude' => 'alerts,minutely,hourly,daily,flags',
|
||||
'units' => 'si',
|
||||
]);
|
||||
|
||||
return $url.$key.'/'.$coords.'?'.$query;
|
||||
$query = [
|
||||
'key' => config('monica.weatherapi_key'),
|
||||
'q' => $coords,
|
||||
'lang' => $lang ?? 'en',
|
||||
];
|
||||
if ($lang !== null && $lang !== 'en') {
|
||||
$query['lang'] = $lang;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch missing longitude/latitude.
|
||||
*
|
||||
* @param Place $place
|
||||
* @return Place|null
|
||||
*/
|
||||
private function fetchGPS(Place $place): ?Place
|
||||
{
|
||||
if (config('monica.enable_geolocation') && ! is_null(config('monica.location_iq_api_key'))) {
|
||||
GetGPSCoordinate::dispatchSync($place);
|
||||
$place->refresh();
|
||||
|
||||
return $place;
|
||||
}
|
||||
|
||||
return null;
|
||||
return Str::of(config('location.weatherapi_url'))->rtrim('/').'?'.http_build_query($query);
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -94,12 +94,12 @@ return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Darksky API Url
|
||||
| Weatherapi Url
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Url to call Darksy api. See https://darksky.net/dev/docs
|
||||
| Url to call Weatherapi.
|
||||
|
|
||||
*/
|
||||
'darksky_url' => env('DARKSKY_URL', 'https://api.darksky.net/forecast/'),
|
||||
'weatherapi_url' => env('WEATHERAPI_URL', 'https://api.weatherapi.com/v1/current.json'),
|
||||
|
||||
];
|
||||
|
||||
+3
-4
@@ -249,11 +249,10 @@ return [
|
||||
| API key for weather data.
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| To provide weather information, we use Darksky.
|
||||
| Darksky provides an api with 1000 free API calls per day.
|
||||
| https://darksky.net/dev/register
|
||||
| To provide weather information, we use WeatherAPI.
|
||||
| See https://www.weatherapi.com/
|
||||
*/
|
||||
'darksky_api_key' => env('DARKSKY_API_KEY', null),
|
||||
'weatherapi_key' => env('WEATHERAPI_KEY', null),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
||||
@@ -110,33 +110,47 @@ $factory->define(App\Models\Account\Weather::class, function (Faker\Generator $f
|
||||
])->id;
|
||||
},
|
||||
'weather_json' => json_decode('
|
||||
{
|
||||
"latitude": 45.487685,
|
||||
"longitude": -73.590259,
|
||||
"timezone": "America\/Toronto",
|
||||
"currently": {
|
||||
"time": 1541637005,
|
||||
"summary": "Mostly Cloudy",
|
||||
"icon": "partly-cloudy-night",
|
||||
"nearestStormDistance": 39,
|
||||
"nearestStormBearing": 307,
|
||||
"precipIntensity": 0,
|
||||
"precipProbability": 0,
|
||||
"temperature": 7.57,
|
||||
"apparentTemperature": 3.82,
|
||||
"dewPoint": 1.24,
|
||||
"humidity": 0.64,
|
||||
"pressure": 1009.91,
|
||||
"windSpeed": 6.98,
|
||||
"windGust": 12.99,
|
||||
"windBearing": 249,
|
||||
"cloudCover": 0.73,
|
||||
"uvIndex": 0,
|
||||
"visibility": 16.09,
|
||||
"ozone": 304.17
|
||||
{
|
||||
"location": {
|
||||
"name": "Le Pre-Saint-Gervais",
|
||||
"region": "Ile-de-France",
|
||||
"country": "France",
|
||||
"lat": 48.89,
|
||||
"lon": 2.39,
|
||||
"tz_id": "Europe\/Paris",
|
||||
"localtime_epoch": 1635605324,
|
||||
"localtime": "2021-10-30 16:48"
|
||||
},
|
||||
"offset": -5
|
||||
}'),
|
||||
"current": {
|
||||
"last_updated_epoch": 1635605100,
|
||||
"last_updated": "2021-10-30 16:45",
|
||||
"temp_c": 13,
|
||||
"temp_f": 55.4,
|
||||
"is_day": 0,
|
||||
"condition": {
|
||||
"text": "Partly cloudy",
|
||||
"icon": "\/\/cdn.weatherapi.com\/weather\/64x64\/night\/116.png",
|
||||
"code": 1003
|
||||
},
|
||||
"wind_mph": 5.6,
|
||||
"wind_kph": 9,
|
||||
"wind_degree": 210,
|
||||
"wind_dir": "SSW",
|
||||
"pressure_mb": 1001,
|
||||
"pressure_in": 29.56,
|
||||
"precip_mm": 0,
|
||||
"precip_in": 0,
|
||||
"humidity": 94,
|
||||
"cloud": 75,
|
||||
"feelslike_c": 11.2,
|
||||
"feelslike_f": 52.1,
|
||||
"vis_km": 10,
|
||||
"vis_miles": 6,
|
||||
"uv": 4,
|
||||
"gust_mph": 17.9,
|
||||
"gust_kph": 28.8
|
||||
}
|
||||
}'),
|
||||
'created_at' => now(),
|
||||
];
|
||||
});
|
||||
|
||||
@@ -441,16 +441,62 @@ return [
|
||||
'emotion_dread' => 'Dread',
|
||||
|
||||
// weather
|
||||
'weather_clear-day' => 'Clear day',
|
||||
'weather_sunny' => 'Sunny',
|
||||
'weather_clear' => 'Clear',
|
||||
'weather_clear-day' => 'Clear',
|
||||
'weather_clear-night' => 'Clear night',
|
||||
'weather_light-drizzle' => 'Light drizzle',
|
||||
'weather_patchy-light-drizzle' => 'Patchy light drizzle',
|
||||
'weather_patchy-light-rain' => 'Patchy light rain',
|
||||
'weather_light-rain' => 'Light rain',
|
||||
'weather_moderate-rain-at-times' => 'Moderate rain at times',
|
||||
'weather_moderate-rain' => 'Moderate rain',
|
||||
'weather_patchy-rain-possible' => 'Patchy rain possible',
|
||||
'weather_heavy-rain-at-times' => 'Heavy rain at times',
|
||||
'weather_heavy-rain' => 'Heavy rain',
|
||||
'weather_light-freezing-rain' => 'Light freezing rain',
|
||||
'weather_moderate-or-heavy-freezing-rain' => 'Moderate or heavy freezing rain',
|
||||
'weather_light-sleet' => 'Light sleet',
|
||||
'weather_moderate-or-heavy-rain-shower' => 'Moderate or heavy rain shower',
|
||||
'weather_light-rain-shower' => 'Light rain shower',
|
||||
'weather_torrential-rain-shower' => 'Torrential rain shower',
|
||||
'weather_rain' => 'Rain',
|
||||
'weather_snow' => 'Snow',
|
||||
'weather_blowing-snow' => 'Blowing snow',
|
||||
'weather_patchy-light-snow' => 'Patchy light snow',
|
||||
'weather_light-snow' => 'Light snow',
|
||||
'weather_patchy-moderate-snow' => 'Patchy moderate snow',
|
||||
'weather_moderate-snow' => 'Moderate snow',
|
||||
'weather_patchy-heavy-snow' => 'Patchy heavy snow',
|
||||
'weather_heavy-snow' => 'Heavy snow',
|
||||
'weather_light-snow-showers' => 'Light snow showers',
|
||||
'weather_moderate-or-heavy-snow-showers' => 'Moderate or heavy snow showers',
|
||||
'weather_patchy-snow-possible' => 'Patchy snow possible',
|
||||
'weather_patchy-sleet-possible' => 'Patchy sleet possible',
|
||||
'weather_moderate-or-heavy-sleet' => 'Moderate or heavy sleet',
|
||||
'weather_light-sleet-showers' => 'Light sleet showers',
|
||||
'weather_moderate-or-heavy-sleet-showers' => 'Moderate or heavy sleet showers',
|
||||
'weather_sleet' => 'Sleet',
|
||||
'weather_wind' => 'Wind',
|
||||
'weather_fog' => 'Fog',
|
||||
'weather_freezing-fog' => 'Freezing fog',
|
||||
'weather_mist' => 'Mist',
|
||||
'weather_blizzard' => 'Blizzard',
|
||||
'weather_overcast' => 'Overcast',
|
||||
'weather_cloudy' => 'Cloudy',
|
||||
'weather_partly-cloudy-day' => 'Partly cloudy day',
|
||||
'weather_partly-cloudy-night' => 'Partly cloudy night',
|
||||
'weather_partly-cloudy-day' => 'Partly cloudy',
|
||||
'weather_partly-cloudy-night' => 'Partly cloudy',
|
||||
'weather_freezing-drizzle' => 'Freezing drizzle',
|
||||
'weather_heavy-freezing-drizzle' => 'Heavy freezing drizzle',
|
||||
'weather_patchy-freezing-drizzle-possible' => 'Patchy freezing drizzle possible',
|
||||
'weather_ice-pellets' => 'Ice pellets',
|
||||
'weather_light-showers-of-ice-pellets' => 'Light showers of ice pellets',
|
||||
'weather_moderate-or-heavy-showers-of-ice-pellets' => 'Moderate or heavy showers of ice pellets',
|
||||
'weather_thundery-outbreaks-possible' => 'Thundery outbreaks possible',
|
||||
'weather_patchy-light-rain-with-thunder' => 'Patchy light rain with thunder',
|
||||
'weather_moderate-or-heavy-rain-with-thunder' => 'Moderate or heavy rain with thunder',
|
||||
'weather_patchy-light-snow-with-thunder' => 'Patchy light snow with thunder',
|
||||
'weather_moderate-or-heavy-snow-with-thunder' => 'Moderate or heavy snow with thunder',
|
||||
'weather_current_temperature_celsius' => ':temperature °C',
|
||||
'weather_current_temperature_fahrenheit' => ':temperature °F',
|
||||
'weather_current_title' => 'Current weather',
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
</div>
|
||||
|
||||
<p class="mb0">
|
||||
{{ $weather->getEmoji() }} {{ trans('app.weather_'.$weather->summary_icon) }} / {{ trans('app.weather_current_temperature_'.auth()->user()->temperature_scale, ['temperature' => $weather->temperature(auth()->user()->temperature_scale)]) }}
|
||||
{{ $weather->emoji }} {{ $weather->summary }} / {{ trans('app.weather_current_temperature_'.auth()->user()->temperature_scale, ['temperature' => $weather->temperature(auth()->user()->temperature_scale)]) }}
|
||||
</p>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@@ -1,27 +1,41 @@
|
||||
{
|
||||
"latitude":34.112456,
|
||||
"longitude":-118.4270732,
|
||||
"timezone":"America/Los_Angeles",
|
||||
"currently":{
|
||||
"time":1545426311,
|
||||
"summary":"Partly Cloudy",
|
||||
"icon":"partly-cloudy-day",
|
||||
"nearestStormDistance":10,
|
||||
"nearestStormBearing":296,
|
||||
"precipIntensity":0,
|
||||
"precipProbability":0,
|
||||
"temperature":67.94,
|
||||
"apparentTemperature":67.94,
|
||||
"dewPoint":41.87,
|
||||
"humidity":0.39,
|
||||
"pressure":1014.57,
|
||||
"windSpeed":3.35,
|
||||
"windGust":6.12,
|
||||
"windBearing":232,
|
||||
"cloudCover":0.28,
|
||||
"uvIndex":2,
|
||||
"visibility":10,
|
||||
"ozone":277.93
|
||||
"location": {
|
||||
"name": "Le Pre-Saint-Gervais",
|
||||
"region": "Ile-de-France",
|
||||
"country": "France",
|
||||
"lat": 48.89,
|
||||
"lon": 2.39,
|
||||
"tz_id": "Europe\/Paris",
|
||||
"localtime_epoch": 1635605324,
|
||||
"localtime": "2021-10-30 16:48"
|
||||
},
|
||||
"offset":-8
|
||||
"current": {
|
||||
"last_updated_epoch": 1635605100,
|
||||
"last_updated": "2021-10-30 16:45",
|
||||
"temp_c": 13,
|
||||
"temp_f": 55.4,
|
||||
"is_day": 0,
|
||||
"condition": {
|
||||
"text": "Partly cloudy",
|
||||
"icon": "\/\/cdn.weatherapi.com\/weather\/64x64\/night\/116.png",
|
||||
"code": 1003
|
||||
},
|
||||
"wind_mph": 5.6,
|
||||
"wind_kph": 9,
|
||||
"wind_degree": 210,
|
||||
"wind_dir": "SSW",
|
||||
"pressure_mb": 1001,
|
||||
"pressure_in": 29.56,
|
||||
"precip_mm": 0,
|
||||
"precip_in": 0,
|
||||
"humidity": 94,
|
||||
"cloud": 75,
|
||||
"feelslike_c": 11.2,
|
||||
"feelslike_f": 52.1,
|
||||
"vis_km": 10,
|
||||
"vis_miles": 6,
|
||||
"uv": 4,
|
||||
"gust_mph": 17.9,
|
||||
"gust_kph": 28.8
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,12 @@ namespace Tests\Unit\Helpers;
|
||||
|
||||
use Tests\FeatureTestCase;
|
||||
use App\Helpers\WeatherHelper;
|
||||
use App\Jobs\GetGPSCoordinate;
|
||||
use App\Models\Contact\Address;
|
||||
use App\Models\Contact\Contact;
|
||||
use Illuminate\Bus\PendingBatch;
|
||||
use App\Jobs\GetWeatherInformation;
|
||||
use Illuminate\Support\Facades\Bus;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
|
||||
class WeatherHelperTest extends FeatureTestCase
|
||||
@@ -17,4 +22,27 @@ class WeatherHelperTest extends FeatureTestCase
|
||||
$contact = factory(Contact::class)->create([]);
|
||||
$this->assertNull(WeatherHelper::getWeatherForAddress($contact->addresses()->first()));
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_dispatch_batch_with_get_coordinates()
|
||||
{
|
||||
config(['monica.enable_geolocation' => true]);
|
||||
config(['monica.location_iq_api_key' => 'test']);
|
||||
config(['monica.enable_weather' => true]);
|
||||
config(['monica.weatherapi_key' => 'test']);
|
||||
|
||||
$fake = Bus::fake();
|
||||
|
||||
$address = factory(Address::class)->create();
|
||||
|
||||
WeatherHelper::getWeatherForAddress($address);
|
||||
|
||||
$fake->assertBatched(function (PendingBatch $pendingBatch) {
|
||||
$this->assertCount(2, $pendingBatch->jobs);
|
||||
$this->assertInstanceOf(GetGPSCoordinate::class, $pendingBatch->jobs[0]);
|
||||
$this->assertInstanceOf(GetWeatherInformation::class, $pendingBatch->jobs[1]);
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Jobs;
|
||||
|
||||
use Tests\TestCase;
|
||||
use Mockery\MockInterface;
|
||||
use App\Models\Account\Place;
|
||||
use Illuminate\Bus\PendingBatch;
|
||||
use App\Jobs\GetWeatherInformation;
|
||||
use Illuminate\Support\Facades\Bus;
|
||||
use Illuminate\Bus\DatabaseBatchRepository;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use App\Services\Instance\Weather\GetWeatherInformation as GetWeatherInformationService;
|
||||
|
||||
class GetWeatherInformationTest extends TestCase
|
||||
{
|
||||
use DatabaseTransactions;
|
||||
|
||||
/** @test */
|
||||
public function it_run_job_weather_information()
|
||||
{
|
||||
$fake = Bus::fake();
|
||||
|
||||
$place = factory(Place::class)->create([
|
||||
'latitude' => '34.112456',
|
||||
'longitude' => '-118.4270732',
|
||||
]);
|
||||
|
||||
$this->mock(GetWeatherInformationService::class, function (MockInterface $mock) use ($place) {
|
||||
$mock->shouldReceive('execute')
|
||||
->once()
|
||||
->withArgs(function ($data) use ($place) {
|
||||
$this->assertEquals([
|
||||
'account_id' => $place->account_id,
|
||||
'place_id' => $place->id,
|
||||
], $data);
|
||||
|
||||
return true;
|
||||
});
|
||||
});
|
||||
|
||||
$pendingBatch = $fake->batch([
|
||||
$job = new GetWeatherInformation($place),
|
||||
]);
|
||||
$batch = $pendingBatch->dispatch();
|
||||
|
||||
$fake->assertBatched(function (PendingBatch $pendingBatch) {
|
||||
$this->assertCount(1, $pendingBatch->jobs);
|
||||
$this->assertInstanceOf(GetWeatherInformation::class, $pendingBatch->jobs->first());
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
$batch = app(DatabaseBatchRepository::class)->store($pendingBatch);
|
||||
$job->withBatchId($batch->id)->handle();
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,7 @@ class WeatherTest extends TestCase
|
||||
$weather = factory(Weather::class)->create();
|
||||
|
||||
$this->assertEquals(
|
||||
7.6,
|
||||
13,
|
||||
$weather->temperature()
|
||||
);
|
||||
}
|
||||
@@ -45,7 +45,7 @@ class WeatherTest extends TestCase
|
||||
$weather = factory(Weather::class)->create();
|
||||
|
||||
$this->assertEquals(
|
||||
7.6,
|
||||
13,
|
||||
$weather->temperature('celsius')
|
||||
);
|
||||
}
|
||||
@@ -56,7 +56,7 @@ class WeatherTest extends TestCase
|
||||
$weather = factory(Weather::class)->create();
|
||||
|
||||
$this->assertEquals(
|
||||
45.6,
|
||||
55.4,
|
||||
$weather->temperature('fahrenheit')
|
||||
);
|
||||
}
|
||||
@@ -67,19 +67,19 @@ class WeatherTest extends TestCase
|
||||
$weather = factory(Weather::class)->create();
|
||||
|
||||
$this->assertEquals(
|
||||
'Mostly Cloudy',
|
||||
'Partly cloudy',
|
||||
$weather->summary
|
||||
);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_gets_current_icon()
|
||||
public function it_gets_current_code()
|
||||
{
|
||||
$weather = factory(Weather::class)->create();
|
||||
|
||||
$this->assertEquals(
|
||||
'partly-cloudy-night',
|
||||
$weather->summaryIcon
|
||||
$weather->summary_code
|
||||
);
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ class WeatherTest extends TestCase
|
||||
|
||||
$this->assertEquals(
|
||||
'🎑',
|
||||
$weather->getEmoji()
|
||||
$weather->emoji
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ use App\Models\Account\Place;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use App\Exceptions\RateLimitedSecondException;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use App\Exceptions\MissingEnvVariableException;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use App\Services\Instance\Geolocalization\GetGPSCoordinate;
|
||||
|
||||
@@ -26,9 +27,8 @@ class GetGPSCoordinateTest extends TestCase
|
||||
'place_id' => $place->id,
|
||||
];
|
||||
|
||||
$place = app(GetGPSCoordinate::class)->execute($request);
|
||||
|
||||
$this->assertNull($place);
|
||||
$this->expectException(MissingEnvVariableException::class);
|
||||
app(GetGPSCoordinate::class)->execute($request);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
@@ -92,6 +92,9 @@ class GetGPSCoordinateTest extends TestCase
|
||||
/** @test */
|
||||
public function it_fails_if_wrong_parameters_are_given()
|
||||
{
|
||||
config(['monica.enable_geolocation' => true]);
|
||||
config(['monica.location_iq_api_key' => 'test']);
|
||||
|
||||
$request = [
|
||||
'account_id' => 111,
|
||||
];
|
||||
|
||||
@@ -4,9 +4,9 @@ namespace Tests\Unit\Services\Instance\Weather;
|
||||
|
||||
use Tests\TestCase;
|
||||
use App\Models\Account\Place;
|
||||
use App\Models\Account\Account;
|
||||
use App\Models\Account\Weather;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use App\Exceptions\NoCoordinatesException;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use App\Exceptions\MissingEnvVariableException;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
@@ -17,7 +17,7 @@ class GetWeatherInformationTest extends TestCase
|
||||
use DatabaseTransactions;
|
||||
|
||||
/** @test */
|
||||
public function it_gets_weather_information()
|
||||
public function it_gets_weather_information_normal()
|
||||
{
|
||||
$place = factory(Place::class)->create([
|
||||
'latitude' => '34.112456',
|
||||
@@ -25,14 +25,15 @@ class GetWeatherInformationTest extends TestCase
|
||||
]);
|
||||
|
||||
config(['monica.enable_weather' => true]);
|
||||
config(['monica.darksky_api_key' => 'test']);
|
||||
config(['monica.weatherapi_key' => 'test']);
|
||||
|
||||
$body = file_get_contents(base_path('tests/Fixtures/Services/Instance/Weather/GetWeatherInformationSampleResponse.json'));
|
||||
Http::fake([
|
||||
'api.darksky.net/forecast/*' => Http::response($body, 200),
|
||||
'api.weatherapi.com/v1/*' => Http::response($body, 200),
|
||||
]);
|
||||
|
||||
$request = [
|
||||
'account_id' => $place->account_id,
|
||||
'place_id' => $place->id,
|
||||
];
|
||||
|
||||
@@ -45,57 +46,7 @@ class GetWeatherInformationTest extends TestCase
|
||||
]);
|
||||
|
||||
$this->assertEquals(
|
||||
'Partly Cloudy',
|
||||
$weather->summary
|
||||
);
|
||||
|
||||
$this->assertInstanceOf(
|
||||
Weather::class,
|
||||
$weather
|
||||
);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_gets_weather_information_for_new_place()
|
||||
{
|
||||
$account = factory(Account::class)->create();
|
||||
$place = factory(Place::class)->create([
|
||||
'account_id' => $account->id,
|
||||
]);
|
||||
|
||||
config(['monica.enable_weather' => true]);
|
||||
config(['monica.darksky_api_key' => 'test']);
|
||||
config(['monica.enable_geolocation' => true]);
|
||||
config(['monica.location_iq_api_key' => 'test']);
|
||||
|
||||
$body = file_get_contents(base_path('tests/Fixtures/Services/Instance/Weather/GetWeatherInformationSampleResponse.json'));
|
||||
$placeBody = file_get_contents(base_path('tests/Fixtures/Services/Account/Place/CreatePlaceSampleResponse.json'));
|
||||
Http::fake([
|
||||
'us1.locationiq.com/v1/*' => Http::response($placeBody, 200),
|
||||
'api.darksky.net/forecast/*' => Http::response($body, 200),
|
||||
]);
|
||||
|
||||
$request = [
|
||||
'place_id' => $place->id,
|
||||
];
|
||||
|
||||
$weather = app(GetWeatherInformation::class)->execute($request);
|
||||
|
||||
$this->assertDatabaseHas('weather', [
|
||||
'id' => $weather->id,
|
||||
'account_id' => $account->id,
|
||||
'place_id' => $place->id,
|
||||
]);
|
||||
$this->assertDatabaseHas('places', [
|
||||
'id' => $place->id,
|
||||
'account_id' => $account->id,
|
||||
'street' => '12',
|
||||
'latitude' => 34.0736204,
|
||||
'longitude' => -118.4003563,
|
||||
]);
|
||||
|
||||
$this->assertEquals(
|
||||
'Partly Cloudy',
|
||||
'Partly cloudy',
|
||||
$weather->summary
|
||||
);
|
||||
|
||||
@@ -124,7 +75,7 @@ class GetWeatherInformationTest extends TestCase
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_cant_get_weather_info_if_darksky_api_key_not_provided()
|
||||
public function it_cant_get_weather_info_if_weatherapi_key_not_provided()
|
||||
{
|
||||
$place = factory(Place::class)->create([
|
||||
'latitude' => '34.112456',
|
||||
@@ -132,9 +83,10 @@ class GetWeatherInformationTest extends TestCase
|
||||
]);
|
||||
|
||||
config(['monica.enable_weather' => true]);
|
||||
config(['monica.darksky_api_key' => null]);
|
||||
config(['monica.weatherapi_key' => null]);
|
||||
|
||||
$request = [
|
||||
'account_id' => $place->account_id,
|
||||
'place_id' => $place->id,
|
||||
];
|
||||
|
||||
@@ -148,21 +100,23 @@ class GetWeatherInformationTest extends TestCase
|
||||
$place = factory(Place::class)->create([]);
|
||||
|
||||
config(['monica.enable_weather' => true]);
|
||||
config(['monica.darksky_api_key' => 'test']);
|
||||
config(['monica.weatherapi_key' => 'test']);
|
||||
config(['monica.enable_geolocation' => false]);
|
||||
|
||||
$request = [
|
||||
'account_id' => $place->account_id,
|
||||
'place_id' => $place->id,
|
||||
];
|
||||
|
||||
$this->assertNull(app(GetWeatherInformation::class)->execute($request));
|
||||
$this->expectException(NoCoordinatesException::class);
|
||||
app(GetWeatherInformation::class)->execute($request);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_fails_if_wrong_parameters_are_given()
|
||||
{
|
||||
config(['monica.enable_weather' => true]);
|
||||
config(['monica.darksky_api_key' => 'test']);
|
||||
config(['monica.weatherapi_key' => 'test']);
|
||||
|
||||
$request = [];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user