schneespur/app/Services/Weather/BrightSkyProvider.php
Michael 2c63440ed8 Revert: move code back to project root from schneespur/ subdirectory
- Reverts the schneespur/ subdirectory restructure (b8e426b)
- Restores package.json and vite.config.js (needed for npm build, were
  removed in an earlier cleanup before the restructure)
- Updates public/build/ assets with current Vite output (new content hashes)
2026-05-17 18:24:26 +00:00

159 lines
4.5 KiB
PHP

<?php
namespace App\Services\Weather;
use Carbon\Carbon;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
class BrightSkyProvider implements WeatherProviderInterface
{
private const API_URL = 'https://api.brightsky.dev/current_weather';
private const TIMEOUT_SECONDS = 5;
public function fetchCurrent(float $lat, float $lon): ?WeatherData
{
try {
$response = Http::timeout(self::TIMEOUT_SECONDS)
->get(self::API_URL, [
'lat' => $lat,
'lon' => $lon,
'units' => 'dwd',
]);
if ($response->failed()) {
Log::info('Weather API returned non-success status', [
'provider' => 'brightsky',
'status' => $response->status(),
'lat' => $lat,
'lon' => $lon,
]);
return null;
}
$data = $response->json();
if (! isset($data['weather'])) {
return null;
}
$weather = $data['weather'];
return new WeatherData(
temperature_c: (float) ($weather['temperature'] ?? 0),
precipitation_mm: (float) ($weather['precipitation_60'] ?? 0),
snowfall_mm: 0.0,
wind_kmh: (float) ($weather['wind_speed_10'] ?? 0),
humidity_percent: (int) ($weather['relative_humidity'] ?? 0),
condition: self::mapCondition(
$weather['condition'] ?? null,
$weather['icon'] ?? null,
),
weather_code: null,
provider: 'brightsky',
observed_at: isset($weather['timestamp'])
? Carbon::parse($weather['timestamp'])
: Carbon::now(),
);
} catch (\Throwable $e) {
Log::info('Weather API request failed', [
'provider' => 'brightsky',
'error' => $e->getMessage(),
'lat' => $lat,
'lon' => $lon,
]);
return null;
}
}
public function testConnection(float $lat, float $lon): array
{
$start = microtime(true);
try {
$response = Http::timeout(self::TIMEOUT_SECONDS)
->get(self::API_URL, [
'lat' => $lat,
'lon' => $lon,
'units' => 'dwd',
]);
$latencyMs = (int) ((microtime(true) - $start) * 1000);
if ($response->failed()) {
return [
'ok' => false,
'message' => __('weather.test_http_error', ['status' => $response->status()]),
'latency_ms' => $latencyMs,
];
}
$data = $response->json();
if (! isset($data['weather'])) {
return [
'ok' => false,
'message' => __('weather.test_missing_data'),
'latency_ms' => $latencyMs,
];
}
return [
'ok' => true,
'message' => __('weather.test_ok'),
'latency_ms' => $latencyMs,
];
} catch (\Throwable $e) {
$latencyMs = (int) ((microtime(true) - $start) * 1000);
return [
'ok' => false,
'message' => $e->getMessage(),
'latency_ms' => $latencyMs,
];
}
}
public function name(): string
{
return 'Bright Sky (DWD)';
}
public function requiresApiKey(): bool
{
return false;
}
public static function mapCondition(?string $condition, ?string $icon): string
{
return match ($condition) {
'dry' => self::mapDryCondition($icon),
'fog' => 'fog',
'rain' => 'rain',
'sleet' => 'rain',
'snow' => 'snow',
'hail' => 'rain-shower',
'thunderstorm' => 'thunderstorm',
default => 'cloudy',
};
}
private static function mapDryCondition(?string $icon): string
{
if ($icon === null) {
return 'cloudy';
}
if (str_contains($icon, 'clear')) {
return 'clear';
}
if (str_contains($icon, 'partly-cloudy') || str_contains($icon, 'cloudy') || str_contains($icon, 'wind')) {
return 'cloudy';
}
return 'cloudy';
}
}