weather api support
This commit is contained in:
@@ -2,12 +2,13 @@ package com.litoralregas.backend;
|
|||||||
|
|
||||||
import com.litoralregas.backend.acquisition.scheduler.AcquisitionSchedulerProperties;
|
import com.litoralregas.backend.acquisition.scheduler.AcquisitionSchedulerProperties;
|
||||||
import com.litoralregas.backend.modbus.ModbusConnectionProperties;
|
import com.litoralregas.backend.modbus.ModbusConnectionProperties;
|
||||||
|
import com.litoralregas.backend.weather.WeatherApiProperties;
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
|
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
@EnableConfigurationProperties({ModbusConnectionProperties.class, AcquisitionSchedulerProperties.class})
|
@EnableConfigurationProperties({ModbusConnectionProperties.class, AcquisitionSchedulerProperties.class, WeatherApiProperties.class})
|
||||||
public class BackendApplication {
|
public class BackendApplication {
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package com.litoralregas.backend.weather;
|
||||||
|
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
|
||||||
|
@ConfigurationProperties(prefix = "weather")
|
||||||
|
public class WeatherApiProperties {
|
||||||
|
|
||||||
|
private String apiKey;
|
||||||
|
private String baseUrl;
|
||||||
|
private int cacheMinutes = 30;
|
||||||
|
|
||||||
|
public String getApiKey() {
|
||||||
|
return apiKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setApiKey(String apiKey) {
|
||||||
|
this.apiKey = apiKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBaseUrl() {
|
||||||
|
return baseUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBaseUrl(String baseUrl) {
|
||||||
|
this.baseUrl = baseUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCacheMinutes() {
|
||||||
|
return cacheMinutes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCacheMinutes(int cacheMinutes) {
|
||||||
|
this.cacheMinutes = cacheMinutes;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package com.litoralregas.backend.weather;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public final class WeatherConditionMapper {
|
||||||
|
|
||||||
|
private static final Map<String, String> CONDITIONS = Map.ofEntries(
|
||||||
|
Map.entry("Sunny", "Ensolarado"),
|
||||||
|
Map.entry("Clear", "Céu limpo"),
|
||||||
|
Map.entry("Partly Cloudy", "Parcialmente nublado"),
|
||||||
|
Map.entry("Cloudy", "Nublado"),
|
||||||
|
Map.entry("Overcast", "Encoberto"),
|
||||||
|
Map.entry("Mist", "Nevoeiro"),
|
||||||
|
Map.entry("Fog", "Nevoeiro"),
|
||||||
|
Map.entry("Freezing fog", "Nevoeiro gelado"),
|
||||||
|
|
||||||
|
Map.entry("Patchy rain nearby", "Possibilidade de chuva"),
|
||||||
|
Map.entry("Light rain", "Chuva fraca"),
|
||||||
|
Map.entry("Moderate rain", "Chuva moderada"),
|
||||||
|
Map.entry("Heavy rain", "Chuva forte"),
|
||||||
|
|
||||||
|
Map.entry("Patchy light rain", "Aguaceiros fracos"),
|
||||||
|
Map.entry("Moderate or heavy rain shower", "Aguaceiros fortes"),
|
||||||
|
|
||||||
|
Map.entry("Thundery outbreaks nearby", "Trovoada próxima"),
|
||||||
|
Map.entry("Patchy light rain with thunder", "Chuva fraca com trovoada"),
|
||||||
|
|
||||||
|
Map.entry("Light drizzle", "Chuvisco fraco"),
|
||||||
|
Map.entry("Moderate drizzle", "Chuvisco moderado"),
|
||||||
|
|
||||||
|
Map.entry("Patchy snow nearby", "Possibilidade de neve"),
|
||||||
|
Map.entry("Light snow", "Neve fraca"),
|
||||||
|
Map.entry("Moderate snow", "Neve moderada"),
|
||||||
|
Map.entry("Heavy snow", "Neve forte")
|
||||||
|
);
|
||||||
|
|
||||||
|
private WeatherConditionMapper() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String normalize(String condition) {
|
||||||
|
if (condition == null || condition.isBlank()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return CONDITIONS.getOrDefault(condition, condition);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package com.litoralregas.backend.weather;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.litoralregas.backend.weather.dto.WeatherConfiguredLocationResponse;
|
||||||
|
import com.litoralregas.backend.weather.dto.WeatherForecastResponse;
|
||||||
|
import com.litoralregas.backend.weather.dto.WeatherLocationUpdateRequest;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
public class WeatherController {
|
||||||
|
|
||||||
|
private final WeatherService weatherService;
|
||||||
|
|
||||||
|
public WeatherController(WeatherService weatherService) {
|
||||||
|
this.weatherService = weatherService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/api/weather/forecast")
|
||||||
|
public WeatherForecastResponse getForecast(
|
||||||
|
@RequestParam(defaultValue = "7") int days
|
||||||
|
) {
|
||||||
|
return weatherService.getConfiguredForecast(days);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/api/weather/location")
|
||||||
|
public WeatherConfiguredLocationResponse getLocation() {
|
||||||
|
return weatherService.getConfiguredLocation();
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/api/weather/location")
|
||||||
|
public WeatherConfiguredLocationResponse updateLocation(
|
||||||
|
@RequestBody WeatherLocationUpdateRequest request
|
||||||
|
) {
|
||||||
|
return weatherService.updateConfiguredLocation(
|
||||||
|
request.latitude(),
|
||||||
|
request.longitude(),
|
||||||
|
request.locationName()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/api/weather/search")
|
||||||
|
public JsonNode search(@RequestParam String query) {
|
||||||
|
return weatherService.search(query);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,256 @@
|
|||||||
|
package com.litoralregas.backend.weather;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.litoralregas.backend.weather.dto.*;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.web.client.RestClient;
|
||||||
|
import org.springframework.web.util.UriComponentsBuilder;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class WeatherService {
|
||||||
|
|
||||||
|
private final WeatherApiProperties properties;
|
||||||
|
private final RestClient restClient;
|
||||||
|
private final Map<String, CachedWeatherResponse> cache = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
private final WeatherSettingsRepository weatherSettingsRepository;
|
||||||
|
|
||||||
|
private volatile Double overrideLatitude;
|
||||||
|
private volatile Double overrideLongitude;
|
||||||
|
private volatile String overrideLocationName;
|
||||||
|
|
||||||
|
public WeatherService(
|
||||||
|
WeatherApiProperties properties,
|
||||||
|
WeatherSettingsRepository weatherSettingsRepository
|
||||||
|
) {
|
||||||
|
this.properties = properties;
|
||||||
|
this.weatherSettingsRepository = weatherSettingsRepository;
|
||||||
|
|
||||||
|
this.restClient = RestClient.builder()
|
||||||
|
.baseUrl(properties.getBaseUrl())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public WeatherForecastResponse getForecast(double latitude, double longitude, int days) {
|
||||||
|
int safeDays = Math.max(1, Math.min(days, 7));
|
||||||
|
|
||||||
|
String q = roundCoordinate(latitude) + "," + roundCoordinate(longitude);
|
||||||
|
String cacheKey = "forecast:" + q + ":" + safeDays;
|
||||||
|
|
||||||
|
JsonNode payload = getCached(cacheKey, () ->
|
||||||
|
restClient.get()
|
||||||
|
.uri(uriBuilder -> uriBuilder
|
||||||
|
.path("/forecast.json")
|
||||||
|
.queryParam("key", properties.getApiKey())
|
||||||
|
.queryParam("q", q)
|
||||||
|
.queryParam("days", safeDays)
|
||||||
|
.queryParam("aqi", "yes")
|
||||||
|
.queryParam("alerts", "yes")
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.retrieve()
|
||||||
|
.body(JsonNode.class)
|
||||||
|
);
|
||||||
|
|
||||||
|
return toForecastResponse(payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonNode search(String query) {
|
||||||
|
String cleanQuery = query == null ? "" : query.trim();
|
||||||
|
|
||||||
|
if (cleanQuery.length() < 2) {
|
||||||
|
throw new IllegalArgumentException("Search query must have at least 2 characters.");
|
||||||
|
}
|
||||||
|
|
||||||
|
String cacheKey = "search:" + cleanQuery.toLowerCase();
|
||||||
|
|
||||||
|
return getCached(cacheKey, () ->
|
||||||
|
restClient.get()
|
||||||
|
.uri(uriBuilder -> uriBuilder
|
||||||
|
.path("/search.json")
|
||||||
|
.queryParam("key", properties.getApiKey())
|
||||||
|
.queryParam("q", cleanQuery)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.retrieve()
|
||||||
|
.body(JsonNode.class)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private JsonNode getCached(String cacheKey, WeatherSupplier supplier) {
|
||||||
|
CachedWeatherResponse cached = cache.get(cacheKey);
|
||||||
|
|
||||||
|
if (cached != null && !cached.isExpired(properties.getCacheMinutes())) {
|
||||||
|
return cached.payload();
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonNode payload = supplier.get();
|
||||||
|
|
||||||
|
cache.put(cacheKey, new CachedWeatherResponse(payload, Instant.now()));
|
||||||
|
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
private interface WeatherSupplier {
|
||||||
|
JsonNode get();
|
||||||
|
}
|
||||||
|
|
||||||
|
private record CachedWeatherResponse(JsonNode payload, Instant storedAt) {
|
||||||
|
boolean isExpired(int cacheMinutes) {
|
||||||
|
return storedAt.plus(Duration.ofMinutes(cacheMinutes)).isBefore(Instant.now());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private WeatherForecastResponse toForecastResponse(JsonNode payload) {
|
||||||
|
JsonNode location = payload.path("location");
|
||||||
|
JsonNode current = payload.path("current");
|
||||||
|
JsonNode forecastDays = payload.path("forecast").path("forecastday");
|
||||||
|
|
||||||
|
List<WeatherDailyDto> daily = new ArrayList<>();
|
||||||
|
|
||||||
|
for (JsonNode dayNode : forecastDays) {
|
||||||
|
JsonNode day = dayNode.path("day");
|
||||||
|
JsonNode astro = dayNode.path("astro");
|
||||||
|
|
||||||
|
daily.add(new WeatherDailyDto(
|
||||||
|
textOrNull(dayNode, "date"),
|
||||||
|
doubleOrNull(day, "maxtemp_c"),
|
||||||
|
doubleOrNull(day, "mintemp_c"),
|
||||||
|
doubleOrNull(day, "avgtemp_c"),
|
||||||
|
doubleOrNull(day, "totalprecip_mm"),
|
||||||
|
intOrNull(day, "daily_chance_of_rain"),
|
||||||
|
doubleOrNull(day, "maxwind_kph"),
|
||||||
|
doubleOrNull(day, "uv"),
|
||||||
|
textOrNull(astro, "sunrise"),
|
||||||
|
textOrNull(astro, "sunset"),
|
||||||
|
toCondition(day.path("condition"))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new WeatherForecastResponse(
|
||||||
|
new WeatherLocationDto(
|
||||||
|
textOrNull(location, "name"),
|
||||||
|
textOrNull(location, "region"),
|
||||||
|
textOrNull(location, "country"),
|
||||||
|
doubleOrNull(location, "lat"),
|
||||||
|
doubleOrNull(location, "lon"),
|
||||||
|
textOrNull(location, "localtime")
|
||||||
|
),
|
||||||
|
new WeatherCurrentDto(
|
||||||
|
doubleOrNull(current, "temp_c"),
|
||||||
|
doubleOrNull(current, "feelslike_c"),
|
||||||
|
intOrNull(current, "humidity"),
|
||||||
|
doubleOrNull(current, "precip_mm"),
|
||||||
|
doubleOrNull(current, "wind_kph"),
|
||||||
|
doubleOrNull(current, "wind_degree"),
|
||||||
|
textOrNull(current, "wind_dir"),
|
||||||
|
doubleOrNull(current, "pressure_mb"),
|
||||||
|
doubleOrNull(current, "uv"),
|
||||||
|
toCondition(current.path("condition"))
|
||||||
|
),
|
||||||
|
daily
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private WeatherConditionDto toCondition(JsonNode condition) {
|
||||||
|
if (condition == null || condition.isMissingNode() || condition.isNull()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new WeatherConditionDto(
|
||||||
|
WeatherConditionMapper.normalize(
|
||||||
|
textOrNull(condition, "text")
|
||||||
|
),
|
||||||
|
normalizeIconUrl(textOrNull(condition, "icon")),
|
||||||
|
intOrNull(condition, "code")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String normalizeIconUrl(String icon) {
|
||||||
|
if (icon == null || icon.isBlank()) return null;
|
||||||
|
|
||||||
|
if (icon.startsWith("//")) {
|
||||||
|
return "https:" + icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
return icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String textOrNull(JsonNode node, String field) {
|
||||||
|
JsonNode value = node.path(field);
|
||||||
|
return value.isMissingNode() || value.isNull() ? null : value.asText();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Double doubleOrNull(JsonNode node, String field) {
|
||||||
|
JsonNode value = node.path(field);
|
||||||
|
return value.isMissingNode() || value.isNull() ? null : value.asDouble();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Integer intOrNull(JsonNode node, String field) {
|
||||||
|
JsonNode value = node.path(field);
|
||||||
|
return value.isMissingNode() || value.isNull() ? null : value.asInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String roundCoordinate(double value) {
|
||||||
|
return String.format(Locale.US, "%.3f", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public WeatherForecastResponse getConfiguredForecast(int days) {
|
||||||
|
WeatherSettings settings = getSettings();
|
||||||
|
|
||||||
|
if (!settings.isEnabled()) {
|
||||||
|
throw new IllegalStateException("Weather forecast is disabled.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return getForecast(
|
||||||
|
settings.getLatitude(),
|
||||||
|
settings.getLongitude(),
|
||||||
|
days
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public WeatherConfiguredLocationResponse getConfiguredLocation() {
|
||||||
|
WeatherSettings settings = getSettings();
|
||||||
|
|
||||||
|
return new WeatherConfiguredLocationResponse(
|
||||||
|
settings.isEnabled(),
|
||||||
|
settings.getLatitude(),
|
||||||
|
settings.getLongitude(),
|
||||||
|
settings.getLocationName()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public WeatherConfiguredLocationResponse updateConfiguredLocation(
|
||||||
|
double latitude,
|
||||||
|
double longitude,
|
||||||
|
String locationName
|
||||||
|
) {
|
||||||
|
WeatherSettings settings = getSettings();
|
||||||
|
|
||||||
|
settings.setLatitude(latitude);
|
||||||
|
settings.setLongitude(longitude);
|
||||||
|
settings.setLocationName(locationName);
|
||||||
|
settings.setUpdatedAt(Instant.now());
|
||||||
|
|
||||||
|
weatherSettingsRepository.save(settings);
|
||||||
|
|
||||||
|
cache.clear();
|
||||||
|
|
||||||
|
return getConfiguredLocation();
|
||||||
|
}
|
||||||
|
|
||||||
|
private WeatherSettings getSettings() {
|
||||||
|
return weatherSettingsRepository.findById(1)
|
||||||
|
.orElseThrow(() ->
|
||||||
|
new IllegalStateException("Weather settings not configured."));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
package com.litoralregas.backend.weather;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "weather_settings")
|
||||||
|
public class WeatherSettings {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
private Integer id;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
private boolean enabled;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
private double latitude;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
private double longitude;
|
||||||
|
|
||||||
|
@Column(name = "location_name", nullable = false)
|
||||||
|
private String locationName;
|
||||||
|
|
||||||
|
@Column(name = "updated_at", nullable = false)
|
||||||
|
private Instant updatedAt;
|
||||||
|
|
||||||
|
public Integer getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(Integer id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEnabled(boolean enabled) {
|
||||||
|
this.enabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getLatitude() {
|
||||||
|
return latitude;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLatitude(double latitude) {
|
||||||
|
this.latitude = latitude;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getLongitude() {
|
||||||
|
return longitude;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLongitude(double longitude) {
|
||||||
|
this.longitude = longitude;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLocationName() {
|
||||||
|
return locationName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLocationName(String locationName) {
|
||||||
|
this.locationName = locationName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Instant getUpdatedAt() {
|
||||||
|
return updatedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUpdatedAt(Instant updatedAt) {
|
||||||
|
this.updatedAt = updatedAt;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.litoralregas.backend.weather;
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
|
public interface WeatherSettingsRepository
|
||||||
|
extends JpaRepository<WeatherSettings, Integer> {
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.litoralregas.backend.weather.dto;
|
||||||
|
|
||||||
|
public record WeatherConditionDto(
|
||||||
|
String text,
|
||||||
|
String icon,
|
||||||
|
Integer code
|
||||||
|
) {}
|
||||||
+8
@@ -0,0 +1,8 @@
|
|||||||
|
package com.litoralregas.backend.weather.dto;
|
||||||
|
|
||||||
|
public record WeatherConfiguredLocationResponse(
|
||||||
|
boolean enabled,
|
||||||
|
double latitude,
|
||||||
|
double longitude,
|
||||||
|
String locationName
|
||||||
|
) {}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.litoralregas.backend.weather.dto;
|
||||||
|
|
||||||
|
public record WeatherCurrentDto(
|
||||||
|
Double temperatureC,
|
||||||
|
Double feelsLikeC,
|
||||||
|
Integer humidity,
|
||||||
|
Double precipitationMm,
|
||||||
|
Double windKph,
|
||||||
|
Double windDegree,
|
||||||
|
String windDirection,
|
||||||
|
Double pressureMb,
|
||||||
|
Double uv,
|
||||||
|
WeatherConditionDto condition
|
||||||
|
) {}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.litoralregas.backend.weather.dto;
|
||||||
|
|
||||||
|
public record WeatherDailyDto(
|
||||||
|
String date,
|
||||||
|
Double maxTemperatureC,
|
||||||
|
Double minTemperatureC,
|
||||||
|
Double averageTemperatureC,
|
||||||
|
Double totalPrecipitationMm,
|
||||||
|
Integer dailyRainChance,
|
||||||
|
Double maxWindKph,
|
||||||
|
Double uv,
|
||||||
|
String sunrise,
|
||||||
|
String sunset,
|
||||||
|
WeatherConditionDto condition
|
||||||
|
) {}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.litoralregas.backend.weather.dto;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public record WeatherForecastResponse(
|
||||||
|
WeatherLocationDto location,
|
||||||
|
WeatherCurrentDto current,
|
||||||
|
List<WeatherDailyDto> daily
|
||||||
|
) {}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.litoralregas.backend.weather.dto;
|
||||||
|
|
||||||
|
public record WeatherLocationDto(
|
||||||
|
String name,
|
||||||
|
String region,
|
||||||
|
String country,
|
||||||
|
Double latitude,
|
||||||
|
Double longitude,
|
||||||
|
String localTime
|
||||||
|
) {}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.litoralregas.backend.weather.dto;
|
||||||
|
|
||||||
|
public record WeatherLocationUpdateRequest(
|
||||||
|
double latitude,
|
||||||
|
double longitude,
|
||||||
|
String locationName
|
||||||
|
) {}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
server:
|
server:
|
||||||
port: 18450
|
port: 18450
|
||||||
|
|
||||||
spring:
|
spring:
|
||||||
application:
|
application:
|
||||||
name: backend
|
name: backend
|
||||||
@@ -21,13 +22,26 @@ litoralregas:
|
|||||||
runtime:
|
runtime:
|
||||||
mode: Local
|
mode: Local
|
||||||
controller-name: Estufa_Litoral
|
controller-name: Estufa_Litoral
|
||||||
|
|
||||||
modbus:
|
modbus:
|
||||||
host: 198.19.0.176
|
host: 198.19.0.176
|
||||||
port: 533
|
port: 533
|
||||||
timeout-millis: 500
|
timeout-millis: 500
|
||||||
max-attempts: 3
|
max-attempts: 3
|
||||||
retry-delay-millis: 1000
|
retry-delay-millis: 1000
|
||||||
|
|
||||||
acquisition:
|
acquisition:
|
||||||
scheduler:
|
scheduler:
|
||||||
enabled: true
|
enabled: true
|
||||||
fixed-delay-millis: 3000
|
fixed-delay-millis: 3000
|
||||||
|
|
||||||
|
weather:
|
||||||
|
enabled: true
|
||||||
|
latitude: 40.4289
|
||||||
|
longitude: -8.7375
|
||||||
|
location-name: Mira
|
||||||
|
|
||||||
|
weather:
|
||||||
|
api-key: 0aa355536b6c469eb4b82226262505
|
||||||
|
base-url: https://api.weatherapi.com/v1
|
||||||
|
cache-minutes: 720
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
CREATE TABLE weather_settings (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
enabled BOOLEAN NOT NULL,
|
||||||
|
latitude DOUBLE NOT NULL,
|
||||||
|
longitude DOUBLE NOT NULL,
|
||||||
|
location_name VARCHAR(255) NOT NULL,
|
||||||
|
updated_at TIMESTAMP NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO weather_settings (
|
||||||
|
id,
|
||||||
|
enabled,
|
||||||
|
latitude,
|
||||||
|
longitude,
|
||||||
|
location_name,
|
||||||
|
updated_at
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
1,
|
||||||
|
TRUE,
|
||||||
|
40.4289,
|
||||||
|
-8.7375,
|
||||||
|
'Mira',
|
||||||
|
CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
Reference in New Issue
Block a user