Skip to content

Commit

Permalink
feat: get weather from weatherapi (#5668)
Browse files Browse the repository at this point in the history
  • Loading branch information
asbiin committed Oct 30, 2021
1 parent 223eb85 commit d19b6ad
Show file tree
Hide file tree
Showing 20 changed files with 473 additions and 227 deletions.
5 changes: 2 additions & 3 deletions .env.example
Expand Up @@ -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
Expand Down
9 changes: 9 additions & 0 deletions app/Exceptions/NoCoordinatesException.php
@@ -0,0 +1,9 @@
<?php

namespace App\Exceptions;

use RuntimeException;

class NoCoordinatesException extends RuntimeException
{
}
37 changes: 21 additions & 16 deletions app/Helpers/WeatherHelper.php
Expand Up @@ -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
{
Expand All @@ -20,16 +22,14 @@ public static function getWeatherForAddress($address): ?Weather
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;
Expand All @@ -39,16 +39,21 @@ public static function getWeatherForAddress($address): ?Weather
* 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();
}
}
7 changes: 6 additions & 1 deletion app/Jobs/GetGPSCoordinate.php
Expand Up @@ -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;
Expand All @@ -14,7 +15,7 @@

class GetGPSCoordinate implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
use Batchable, Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

/**
* @var Place
Expand Down Expand Up @@ -64,6 +65,10 @@ public function middleware()
*/
public function handle()
{
if (($batch = $this->batch()) !== null && $batch->cancelled()) {
return;
}

try {
app(GetGPSCoordinateService::class)->execute([
'account_id' => $this->place->account_id,
Expand Down
67 changes: 67 additions & 0 deletions app/Jobs/GetWeatherInformation.php
@@ -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,
]);
}
}
}
95 changes: 79 additions & 16 deletions app/Models/Account/Weather.php
Expand Up @@ -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;
Expand All @@ -16,6 +18,8 @@ class Weather extends Model
* @var array
*/
protected $fillable = [
'account_id',
'place_id',
'weather_json',
];

Expand Down Expand Up @@ -56,58 +60,102 @@ public function place()
}

/**
* Get the weather summary.
* Get the weather code.
*
* @return string
*/
public function getSummaryAttribute($value)
public function getSummaryCodeAttribute()
{
$json = $this->weather_json;

return $json['currently']['summary'];
// 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 icon.
* Get the weather summary.
*
* @return string
*/
public function getSummaryIconAttribute($value)
public function getSummaryAttribute()
{
$json = $this->weather_json;

return $json['currently']['icon'];
return trans('app.weather_'.$this->summary_code);
}

/**
* Get the emoji representing the weather.
* Get the weather icon.
*
* @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;
case 'wind':
$string = '💨';
break;
case 'fog':
case 'mist':
case 'blizzard':
case 'freezing-fog':
$string = '🌫️';
break;
case 'overcast':
case 'cloudy':
$string = '☁️';
break;
Expand All @@ -117,6 +165,21 @@ public function getEmoji()
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;
Expand All @@ -137,10 +200,10 @@ public function temperature($scale = 'celsius')
{
$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);
Expand Down
4 changes: 2 additions & 2 deletions app/Models/Contact/Contact.php
Expand Up @@ -1490,9 +1490,9 @@ public function setStayInTouchTriggerDate($frequency, $triggerDate = null)
* 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());
}
Expand Down

0 comments on commit d19b6ad

Please sign in to comment.