Compare commits
No commits in common. "65068e44a5f37e367aa61c3019b3d0d3aec5abd2" and "2bf517ccd8b038d166f723334abcbfee769697a7" have entirely different histories.
65068e44a5
...
2bf517ccd8
@ -4,4 +4,3 @@ rss_key = "rss-key-for-ci"
|
|||||||
limits = { file = "10 MiB", data-form = "10 MiB"}
|
limits = { file = "10 MiB", data-form = "10 MiB"}
|
||||||
smtp_pw = "8kIjlLH79Ky6D3jQ"
|
smtp_pw = "8kIjlLH79Ky6D3jQ"
|
||||||
usage_log_path = "./usage.txt"
|
usage_log_path = "./usage.txt"
|
||||||
openweathermap_key = "c8dab8f91b5b815d76e9879cbaecd8d5"
|
|
||||||
|
@ -186,11 +186,3 @@ CREATE TABLE IF NOT EXISTS "waterlevel" (
|
|||||||
"tumin" INTEGER NOT NULL,
|
"tumin" INTEGER NOT NULL,
|
||||||
"tumittel" INTEGER NOT NULL
|
"tumittel" INTEGER NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS "weather" (
|
|
||||||
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
|
|
||||||
"day" DATE NOT NULL,
|
|
||||||
"max_temp" FLOAT NOT NULL,
|
|
||||||
"wind_gust" FLOAT NOT NULL,
|
|
||||||
"rain_mm" FLOAT NOT NULL
|
|
||||||
);
|
|
||||||
|
12
src/main.rs
12
src/main.rs
@ -4,9 +4,9 @@ use std::str::FromStr;
|
|||||||
|
|
||||||
#[cfg(feature = "rest")]
|
#[cfg(feature = "rest")]
|
||||||
use rot::rest;
|
use rot::rest;
|
||||||
|
use rot::scheduled;
|
||||||
#[cfg(feature = "rowing-tera")]
|
#[cfg(feature = "rowing-tera")]
|
||||||
use rot::tera;
|
use rot::tera;
|
||||||
use rot::{scheduled, tera::Config};
|
|
||||||
|
|
||||||
use sqlx::{pool::PoolOptions, sqlite::SqliteConnectOptions, ConnectOptions};
|
use sqlx::{pool::PoolOptions, sqlite::SqliteConnectOptions, ConnectOptions};
|
||||||
|
|
||||||
@ -27,7 +27,9 @@ async fn rocket() -> _ {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let rocket = rocket::build().manage(db.clone());
|
scheduled::schedule(&db);
|
||||||
|
|
||||||
|
let rocket = rocket::build().manage(db);
|
||||||
|
|
||||||
#[cfg(feature = "rowing-tera")]
|
#[cfg(feature = "rowing-tera")]
|
||||||
let rocket = tera::config(rocket);
|
let rocket = tera::config(rocket);
|
||||||
@ -35,11 +37,5 @@ async fn rocket() -> _ {
|
|||||||
#[cfg(feature = "rest")]
|
#[cfg(feature = "rest")]
|
||||||
let rocket = rest::config(rocket);
|
let rocket = rest::config(rocket);
|
||||||
|
|
||||||
let config: Config = rocket
|
|
||||||
.figment()
|
|
||||||
.extract()
|
|
||||||
.expect("Config extraction failed");
|
|
||||||
scheduled::schedule(&db, &config);
|
|
||||||
|
|
||||||
rocket
|
rocket
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,6 @@ use self::{
|
|||||||
planned_event::{PlannedEvent, PlannedEventWithUserAndTriptype},
|
planned_event::{PlannedEvent, PlannedEventWithUserAndTriptype},
|
||||||
trip::{Trip, TripWithUserAndType},
|
trip::{Trip, TripWithUserAndType},
|
||||||
waterlevel::Waterlevel,
|
waterlevel::Waterlevel,
|
||||||
weather::Weather,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub mod boat;
|
pub mod boat;
|
||||||
@ -30,7 +29,6 @@ pub mod triptype;
|
|||||||
pub mod user;
|
pub mod user;
|
||||||
pub mod usertrip;
|
pub mod usertrip;
|
||||||
pub mod waterlevel;
|
pub mod waterlevel;
|
||||||
pub mod weather;
|
|
||||||
|
|
||||||
#[derive(Serialize, Debug)]
|
#[derive(Serialize, Debug)]
|
||||||
pub struct Day {
|
pub struct Day {
|
||||||
@ -39,7 +37,6 @@ pub struct Day {
|
|||||||
trips: Vec<TripWithUserAndType>,
|
trips: Vec<TripWithUserAndType>,
|
||||||
is_pinned: bool,
|
is_pinned: bool,
|
||||||
max_waterlevel: Option<i64>,
|
max_waterlevel: Option<i64>,
|
||||||
weather: Option<Weather>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Day {
|
impl Day {
|
||||||
@ -51,7 +48,6 @@ impl Day {
|
|||||||
trips: Trip::get_pinned_for_day(db, day).await,
|
trips: Trip::get_pinned_for_day(db, day).await,
|
||||||
is_pinned,
|
is_pinned,
|
||||||
max_waterlevel: Waterlevel::max_waterlevel_for_day(db, day).await,
|
max_waterlevel: Waterlevel::max_waterlevel_for_day(db, day).await,
|
||||||
weather: Weather::find_by_day(db, day).await,
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Self {
|
Self {
|
||||||
@ -60,7 +56,6 @@ impl Day {
|
|||||||
trips: Trip::get_for_day(db, day).await,
|
trips: Trip::get_for_day(db, day).await,
|
||||||
is_pinned,
|
is_pinned,
|
||||||
max_waterlevel: Waterlevel::max_waterlevel_for_day(db, day).await,
|
max_waterlevel: Waterlevel::max_waterlevel_for_day(db, day).await,
|
||||||
weather: Weather::find_by_day(db, day).await,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,56 +0,0 @@
|
|||||||
use std::ops::DerefMut;
|
|
||||||
|
|
||||||
use chrono::NaiveDate;
|
|
||||||
use rocket::serde::{Deserialize, Serialize};
|
|
||||||
use sqlx::{FromRow, Sqlite, SqlitePool, Transaction};
|
|
||||||
|
|
||||||
#[derive(FromRow, Debug, Serialize, Deserialize, PartialEq, Clone)]
|
|
||||||
pub struct Weather {
|
|
||||||
pub id: i64,
|
|
||||||
pub day: NaiveDate,
|
|
||||||
pub max_temp: f64,
|
|
||||||
pub wind_gust: f64,
|
|
||||||
pub rain_mm: f64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Weather {
|
|
||||||
pub async fn find_by_day(db: &SqlitePool, day: NaiveDate) -> Option<Self> {
|
|
||||||
sqlx::query_as!(Self, "SELECT * FROM weather WHERE day = ?", day)
|
|
||||||
.fetch_one(db)
|
|
||||||
.await
|
|
||||||
.ok()
|
|
||||||
}
|
|
||||||
pub async fn find_by_id_tx(db: &mut Transaction<'_, Sqlite>, day: NaiveDate) -> Option<Self> {
|
|
||||||
sqlx::query_as!(Self, "SELECT * FROM weather WHERE day = ?", day)
|
|
||||||
.fetch_one(db.deref_mut())
|
|
||||||
.await
|
|
||||||
.ok()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn create(
|
|
||||||
db: &mut Transaction<'_, Sqlite>,
|
|
||||||
day: NaiveDate,
|
|
||||||
max_temp: f64,
|
|
||||||
wind_gust: f64,
|
|
||||||
rain_mm: f64,
|
|
||||||
) -> Result<(), String> {
|
|
||||||
sqlx::query!(
|
|
||||||
"INSERT INTO weather(day, max_temp, wind_gust, rain_mm) VALUES (?,?,?,?)",
|
|
||||||
day,
|
|
||||||
max_temp,
|
|
||||||
wind_gust,
|
|
||||||
rain_mm
|
|
||||||
)
|
|
||||||
.execute(db.deref_mut())
|
|
||||||
.await
|
|
||||||
.map_err(|e| e.to_string())?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn delete_all(db: &mut Transaction<'_, Sqlite>) {
|
|
||||||
sqlx::query!("DELETE FROM weather;")
|
|
||||||
.execute(db.deref_mut())
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +1,4 @@
|
|||||||
mod waterlevel;
|
mod waterlevel;
|
||||||
mod weather;
|
|
||||||
|
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
@ -7,15 +6,11 @@ use job_scheduler_ng::{Job, JobScheduler};
|
|||||||
use rocket::tokio::{self, task, time};
|
use rocket::tokio::{self, task, time};
|
||||||
use sqlx::SqlitePool;
|
use sqlx::SqlitePool;
|
||||||
|
|
||||||
use crate::tera::Config;
|
pub fn schedule(db: &SqlitePool) {
|
||||||
|
|
||||||
pub fn schedule(db: &SqlitePool, config: &Config) {
|
|
||||||
let db = db.clone();
|
let db = db.clone();
|
||||||
let openweathermap_key = config.openweathermap_key.clone();
|
|
||||||
|
|
||||||
tokio::task::spawn(async {
|
tokio::task::spawn(async {
|
||||||
waterlevel::update(&db).await.unwrap();
|
waterlevel::update(&db).await.unwrap();
|
||||||
weather::update(&db, &openweathermap_key).await.unwrap();
|
|
||||||
|
|
||||||
let mut sched = JobScheduler::new();
|
let mut sched = JobScheduler::new();
|
||||||
|
|
||||||
@ -27,9 +22,6 @@ pub fn schedule(db: &SqlitePool, config: &Config) {
|
|||||||
task::block_in_place(|| {
|
task::block_in_place(|| {
|
||||||
tokio::runtime::Handle::current().block_on(async {
|
tokio::runtime::Handle::current().block_on(async {
|
||||||
waterlevel::update(&db_clone).await.unwrap();
|
waterlevel::update(&db_clone).await.unwrap();
|
||||||
weather::update(&db_clone, &openweathermap_key)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
@ -1,126 +0,0 @@
|
|||||||
use chrono::DateTime;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use sqlx::SqlitePool;
|
|
||||||
|
|
||||||
use crate::model::weather::Weather;
|
|
||||||
|
|
||||||
pub async fn update(db: &SqlitePool, api_key: &str) -> Result<(), String> {
|
|
||||||
let mut tx = db.begin().await.unwrap();
|
|
||||||
|
|
||||||
// 1. Delete weather data
|
|
||||||
Weather::delete_all(&mut tx).await;
|
|
||||||
|
|
||||||
// 2. Fetch
|
|
||||||
let data = fetch(api_key)?;
|
|
||||||
for d in data.daily {
|
|
||||||
let Some(date) = DateTime::from_timestamp(d.dt, 0) else {
|
|
||||||
println!("Skipping {} because convertion to datetime failed", d.dt);
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
let max_temp = d.temp.max;
|
|
||||||
let wind_gust = d.wind_gust;
|
|
||||||
let Some(rain_mm) = d.rain else {
|
|
||||||
println!(
|
|
||||||
"Skipping weather import of {} as there's no rain prognosed",
|
|
||||||
date
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
Weather::create(
|
|
||||||
&mut tx,
|
|
||||||
date.naive_utc().into(),
|
|
||||||
max_temp,
|
|
||||||
wind_gust,
|
|
||||||
rain_mm,
|
|
||||||
)
|
|
||||||
.await?
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Save in DB
|
|
||||||
tx.commit().await.unwrap();
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
||||||
struct Data {
|
|
||||||
lat: f64,
|
|
||||||
lon: f64,
|
|
||||||
timezone: String,
|
|
||||||
timezone_offset: i64,
|
|
||||||
daily: Vec<Daily>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
||||||
struct Daily {
|
|
||||||
dt: i64,
|
|
||||||
sunrise: i64,
|
|
||||||
sunset: i64,
|
|
||||||
moonrise: i64,
|
|
||||||
moonset: i64,
|
|
||||||
moon_phase: f64,
|
|
||||||
summary: String,
|
|
||||||
temp: Temp,
|
|
||||||
feels_like: FeelsLike,
|
|
||||||
pressure: i64,
|
|
||||||
humidity: i64,
|
|
||||||
dew_point: f64,
|
|
||||||
wind_speed: f64,
|
|
||||||
wind_deg: i64,
|
|
||||||
wind_gust: f64,
|
|
||||||
weather: Vec<DailyWeather>,
|
|
||||||
clouds: i64,
|
|
||||||
pop: f64,
|
|
||||||
rain: Option<f64>,
|
|
||||||
uvi: f64,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
||||||
struct Temp {
|
|
||||||
day: f64,
|
|
||||||
min: f64,
|
|
||||||
max: f64,
|
|
||||||
night: f64,
|
|
||||||
eve: f64,
|
|
||||||
morn: f64,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
||||||
struct FeelsLike {
|
|
||||||
day: f64,
|
|
||||||
night: f64,
|
|
||||||
eve: f64,
|
|
||||||
morn: f64,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
||||||
struct DailyWeather {
|
|
||||||
id: i64,
|
|
||||||
main: String,
|
|
||||||
description: String,
|
|
||||||
icon: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fetch(api_key: &str) -> Result<Data, String> {
|
|
||||||
let url = format!("https://api.openweathermap.org/data/3.0/onecall?lat=48.31970&lon=14.29451&units=metric&exclude=current,minutely,hourly,alert&appid={api_key}");
|
|
||||||
|
|
||||||
match ureq::get(&url).call() {
|
|
||||||
Ok(response) => {
|
|
||||||
let data: Result<Data, _> = response.into_json();
|
|
||||||
|
|
||||||
if let Ok(data) = data {
|
|
||||||
return Ok(data);
|
|
||||||
} else {
|
|
||||||
return Err(format!(
|
|
||||||
"Failed to parse the json received by {url}: {}",
|
|
||||||
data.err().unwrap()
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(_) => {
|
|
||||||
return Err(format!(
|
|
||||||
"Could not fetch {url}, do you have internet? Maybe their server is down?"
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -166,7 +166,6 @@ pub struct Config {
|
|||||||
rss_key: String,
|
rss_key: String,
|
||||||
smtp_pw: String,
|
smtp_pw: String,
|
||||||
usage_log_path: String,
|
usage_log_path: String,
|
||||||
pub openweathermap_key: String,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn config(rocket: Rocket<Build>) -> Rocket<Build> {
|
pub fn config(rocket: Rocket<Build>) -> Rocket<Build> {
|
||||||
|
@ -92,18 +92,9 @@
|
|||||||
<h2 class="font-bold uppercase tracking-wide text-center rounded-t-md {% if day.is_pinned %} text-white bg-primary-950 {% else %} text-primary-950 dark:text-white bg-gray-200 dark:bg-primary-950 bg-opacity-80 {% endif %} text-lg px-3 py-3 ">
|
<h2 class="font-bold uppercase tracking-wide text-center rounded-t-md {% if day.is_pinned %} text-white bg-primary-950 {% else %} text-primary-950 dark:text-white bg-gray-200 dark:bg-primary-950 bg-opacity-80 {% endif %} text-lg px-3 py-3 ">
|
||||||
{{ day.day| date(format="%d.%m.%Y") }}
|
{{ day.day| date(format="%d.%m.%Y") }}
|
||||||
<small class="inline-block ml-1 text-xs {% if day.is_pinned %} text-gray-200 {% else %} text-gray-500 dark:text-gray-100 {% endif %}">{{ day.day | date(format="%A", locale="de_AT") }}
|
<small class="inline-block ml-1 text-xs {% if day.is_pinned %} text-gray-200 {% else %} text-gray-500 dark:text-gray-100 {% endif %}">{{ day.day | date(format="%A", locale="de_AT") }}
|
||||||
{% if day.max_waterlevel %}
|
{% if day.max_waterlevel %}• <a href="https://hydro.ooe.gv.at/#/overview/Wasserstand/station/16668/Linz/Wasserstand" target="_blank" title="Prognostizierter maximaler Wasserstand am {{ day.day | date(format="%A", locale="de_AT") }}: {{ day.max_waterlevel }} cm">🌊{{ day.max_waterlevel }} cm</a>{% endif %}
|
||||||
• <a href="https://hydro.ooe.gv.at/#/overview/Wasserstand/station/16668/Linz/Wasserstand"
|
|
||||||
target="_blank"
|
|
||||||
title="Prognostizierter maximaler Wasserstand am {{ day.day | date(format="%A", locale="de_AT") }}: {{ day.max_waterlevel }} cm">🌊{{ day.max_waterlevel }} cm</a>
|
|
||||||
{% endif %}
|
|
||||||
</small>
|
</small>
|
||||||
</h2>
|
</h2>
|
||||||
{% if day.weather %}
|
|
||||||
<div class="bg-gray-300 rounded text-center">
|
|
||||||
Max temp: {{ day.weather.max_temp }}° • Windböe: {{ day.weather.wind_gust }} km/h • Regen: {{ day.weather.rain_mm }} mm
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% if day.planned_events | length > 0 or day.trips | length > 0 %}
|
{% if day.planned_events | length > 0 or day.trips | length > 0 %}
|
||||||
<div class="grid grid-cols-1 gap-3 mb-3">
|
<div class="grid grid-cols-1 gap-3 mb-3">
|
||||||
{# --- START Events --- #}
|
{# --- START Events --- #}
|
||||||
|
Loading…
Reference in New Issue
Block a user