Compare commits

...

28 Commits
v0.2.7 ... main

Author SHA1 Message Date
Paul van Tilburg 88c59cdb1f
Bump the version to 0.2.11
Check, lint and test using Cargo / Check, lint and test (push) Successful in 4m18s Details
Release / Release (push) Successful in 1m3s Details
Release / Release Rust crate (push) Successful in 3m1s Details
Release / Release Debian package (push) Successful in 5m12s Details
2024-02-27 16:07:22 +01:00
Paul van Tilburg cad766b520
Update the changelog 2024-02-27 16:07:19 +01:00
Paul van Tilburg e62699c102
Tweak/fix tests; reduce required accuracy for geocoded coordinates
Also somebody seems to have slightly moved Eindhoven.
2024-02-27 16:06:56 +01:00
Paul van Tilburg f32f67dbf4
Fix clippy issue 2024-02-27 16:00:57 +01:00
Paul van Tilburg d1e43a7aa7
Cargo update; fixes several security advisories
Fixes RUSTSEC-2024-0003 and RUSTSEC-2023-0072.
2024-02-27 15:59:44 +01:00
Paul van Tilburg c2450267e0
Bump the version to 0.2.10
Check, lint and test using Cargo / Check, lint and test (push) Successful in 5m42s Details
Release / Release (push) Successful in 1m35s Details
Release / Release Rust crate (push) Successful in 4m10s Details
Release / Release Debian package (push) Successful in 6m3s Details
2023-11-03 10:41:56 +01:00
Paul van Tilburg 087ecf00f1
Update the changelog 2023-11-03 10:40:43 +01:00
Paul van Tilburg f8ea25c516
Bump the dependency on cached to 0.46.0 2023-11-03 10:39:49 +01:00
Paul van Tilburg f830d34464
Cargo update; fixes RUSTSEC-2020-0071 and RUSTSEC-2023-0044
Fix the tests for small changes in Rocket 0.5-rc.4.
Also fix the usage of a deprecate method.
2023-11-03 10:39:47 +01:00
Paul van Tilburg ff10cc19e8
Correct Debian package file pattern
Check, lint and test using Cargo / Check, lint and test (push) Successful in 5m34s Details
2023-08-25 21:23:11 +02:00
Paul van Tilburg 1211fea46a
Bump the version to 0.2.9
Check, lint and test using Cargo / Check, lint and test (push) Successful in 6m10s Details
Release / Release (push) Successful in 1m47s Details
Release / Release Rust crate (push) Successful in 4m28s Details
Release / Release Debian package (push) Failing after 6m22s Details
2023-08-25 20:48:46 +02:00
Paul van Tilburg 182521aab7
Update the changelog 2023-08-25 20:48:17 +02:00
Paul van Tilburg dadf5d3147
Fix clippy issue
Check, lint and test using Cargo / Check, lint and test (push) Successful in 6m9s Details
2023-08-25 20:24:19 +02:00
Paul van Tilburg 4b506541f3
Build and release a Debian package in a separate job
Check, lint and test using Cargo / Check, lint and test (push) Failing after 3m34s Details
Release it to the package repository instead of attaching to the release.
Also add the relevant part of the changelog as release notes to the
release and fix some schema-related issues.
2023-08-25 20:15:39 +02:00
Paul van Tilburg 47e28a7098
Cargo update 2023-08-25 20:06:56 +02:00
Paul van Tilburg 07e0701106
Bump the version to 0.2.8
Check, lint and test using Cargo / Check, lint and test (push) Successful in 12m45s Details
Release / Release (push) Successful in 12m16s Details
Release / Release crate (push) Successful in 10m0s Details
2023-06-04 12:13:46 +02:00
Paul van Tilburg 91d5500c86
Update the changelog 2023-06-04 12:13:19 +02:00
Paul van Tilburg 9b3c11ee76
Cargo update 2023-06-04 12:12:54 +02:00
Paul van Tilburg 27e1ac726c
Bump dependency on cached to 0.44.0 2023-06-04 12:12:54 +02:00
Paul van Tilburg 3047cf74c2
No longer configure using a sparse Cargo index for crates.io
This is the default since Rust 1.70.
2023-06-04 12:02:35 +02:00
Paul van Tilburg 44474aa545
Tweak README
Check, lint and test using Cargo / Check, lint and test (push) Successful in 5m44s Details
2023-05-29 16:38:42 +02:00
Paul van Tilburg 50b0e94839
Properly attribute the PAQI metric
Check, lint and test using Cargo / Check, lint and test (push) Successful in 5m45s Details
2023-05-29 16:37:16 +02:00
Paul van Tilburg 1010311403
Don't provide the map for the PAQI metric (it is pollen only) 2023-05-29 16:36:02 +02:00
Paul van Tilburg d16699636b Merge pull request 'Print the version on lift off and add version endpoint' (#30) from 29-print-version-add-endpoint into main
Check, lint and test using Cargo / Check, lint and test (push) Successful in 5m52s Details
Reviewed-on: #30
2023-05-29 16:14:56 +02:00
Paul van Tilburg 38fb28c248 Add a /version API endpoint
Check, lint and test using Cargo / Check, lint and test (pull_request) Successful in 6m5s Details
Check, lint and test using Cargo / Check, lint and test (push) Has been cancelled Details
* Introduce the `VersionInfo` struct, build from the vergen environment
  variables
* Add the `version` handler to construct and return the version info
* Update the README
2023-05-29 15:48:36 +02:00
Paul van Tilburg 7c2b012e95 Print the version on lift off 2023-05-29 15:48:36 +02:00
Paul van Tilburg ab6001f072 Use the vergen crate to generate version information
* Add depend on the `vergen` crate (only use the `build`, `git` and
  `gitcl` features)
* Add the build script `build.rs` to setup the environment variables
  from the build system
2023-05-29 15:48:36 +02:00
Paul van Tilburg 9742331f6d
Annote the map key colors in the comments
Check, lint and test using Cargo / Check, lint and test (push) Successful in 5m42s Details
2023-05-26 20:44:24 +02:00
11 changed files with 1011 additions and 729 deletions

View File

@ -21,9 +21,6 @@ jobs:
override: true
components: rustfmt, clippy
- name: Use sparse Cargo index for crates.io
run: echo -e '[registries.crates-io]\nprotocol = "sparse"' >> /root/.cargo/config.toml
- name: Run cargo check
uses: https://github.com/actions-rs/cargo@v1
with:

View File

@ -2,7 +2,8 @@ name: "Release"
on:
push:
tags: "v*"
tags:
- "v*"
jobs:
release:
@ -20,25 +21,16 @@ jobs:
echo "Releasing version: $VERSION"
echo "VERSION=$VERSION" >> $GITHUB_ENV
- name: Install Rust stable toolchain
uses: https://github.com/actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- name: Use sparse Cargo index for crates.io
run: echo -e '[registries.crates-io]\nprotocol = "sparse"' >> /root/.cargo/config.toml
- name: Install cargo-deb
uses: https://github.com/brndnmtthws/rust-action-cargo-binstall@v1
with:
packages: cargo-deb
- name: Run cargo-deb
uses: https://github.com/actions-rs/cargo@v1
with:
command: deb
- name: Get the release notes from the changelog
run: |
EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64)
RELEASE_NOTES=$(sed -n -e "/^## \[$VERSION\]/,/^## \[/{//"'!'"p;}" CHANGELOG.md | sed -e '1d;$d')
echo "Release notes:"
echo
echo "$RELEASE_NOTES"
echo "RELEASE_NOTES<<$EOF" >> "$GITHUB_ENV"
echo "$RELEASE_NOTES" >> "$GITHUB_ENV"
echo "$EOF" >> "$GITHUB_ENV"
- name: Install Go
uses: actions/setup-go@v4
@ -50,11 +42,12 @@ jobs:
with:
# This is available by default.
api_key: '${{ secrets.RELEASE_TOKEN }}'
files: target/debian/sinoptik*.deb
files: FIXME
title: 'Release ${{ env.VERSION }}'
body: '${{ env.RELEASE_NOTES }}'
release-crate:
name: "Release crate"
name: "Release Rust crate"
runs-on: debian-latest
steps:
- name: Checkout sources
@ -83,3 +76,37 @@ jobs:
with:
command: publish
args: --registry luon
release-deb:
name: "Release Debian package"
runs-on: debian-latest
steps:
- name: Checkout sources
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Install Rust stable toolchain
uses: https://github.com/actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- name: Install cargo-deb
uses: https://github.com/brndnmtthws/rust-action-cargo-binstall@v1
with:
packages: cargo-deb
- name: Run cargo-deb
uses: https://github.com/actions-rs/cargo@v1
with:
command: deb
- name: Publish Debian package
env:
DEB_REPO_TOKEN: '${{ secrets.DEB_REPO_TOKEN }}'
run: |
curl --config <(printf "user=%s:%s" paul "${DEB_REPO_TOKEN}") \
--upload-file target/debian/sinoptik*.deb \
https://git.luon.net/api/packages/paul/debian/pool/bookworm/main/upload

View File

@ -7,6 +7,66 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [0.2.11] - 2024-02-27
### Security
* Updated dependencies, fixes security advisories:
* [RUSTSEC-2024-0003](https://rustsec.org/advisories/RUSTSEC-2024-0003)
* [RUSTSEC-2023-0072](https://rustsec.org/advisories/RUSTSEC-2024-0072)
### Fixed
* Fix clippy issue
* Tweak/fix tests; reduce required accuracy for geocoded coordinates
## [0.2.10] - 2023-11-03
### Security
* Update dependencies
([RUSTSEC-2020-0071](https://rustsec.org/advisories/RUSTSEC-2020-0071.html),
[RUSTSEC-2023-0044](https://rustsec.org/advisories/RUSTSEC-2023-0044.html))
### Changed
* Switch to Rocket 0.5 RC4
* Update dependency on `cached`
### Fixed
* Fix clippy issues
## [0.2.9] - 2023-08-25
### Changed
* Update release Gitea Actions workflow; add seperate job to release Debian
package to the new repository
### Security
* Update dependencies ([RUSTSEC-2023-0044](https://rustsec.org/advisories/RUSTSEC-2023-0044))
## [0.2.8] - 2023-06-05
### Added
* Print the version on lift off (#30)
* Add a `/version` endpoint to the API (#30)
### Changed
* Update dependency on `cached`
### Fixed
* Properly attribute the PAQI metric in its description(s)
### Removed
* No longer provide a map for the PAQI metric; the map used is only for pollen
## [0.2.7] - 2023-05-26
### Fixed
@ -112,7 +172,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
Initial release.
[Unreleased]: https://git.luon.net/paul/sinoptik/compare/v0.2.7...HEAD
[Unreleased]: https://git.luon.net/paul/sinoptik/compare/v0.2.11...HEAD
[0.2.11]: https://git.luon.net/paul/sinoptik/compare/v0.2.10...v0.2.11
[0.2.10]: https://git.luon.net/paul/sinoptik/compare/v0.2.9...v0.2.10
[0.2.9]: https://git.luon.net/paul/sinoptik/compare/v0.2.8...v0.2.9
[0.2.8]: https://git.luon.net/paul/sinoptik/compare/v0.2.7...v0.2.8
[0.2.7]: https://git.luon.net/paul/sinoptik/compare/v0.2.6...v0.2.7
[0.2.6]: https://git.luon.net/paul/sinoptik/compare/v0.2.5...v0.2.6
[0.2.5]: https://git.luon.net/paul/sinoptik/compare/v0.2.4...v0.2.5

1446
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
[package]
name = "sinoptik"
version = "0.2.7"
version = "0.2.11"
authors = [
"Admar Schoonen <admar@luon.net",
"Paul van Tilburg <paul@luon.net>"
@ -12,7 +12,7 @@ repository = "https://git.luon.net/paul/sinoptik"
license = "MIT"
[dependencies]
cached = { version = "0.42.0", features = ["async"] }
cached = { version = "0.46.0", features = ["async"] }
chrono = "0.4.19"
chrono-tz = "0.8.1"
csv = "1.1.6"
@ -22,6 +22,9 @@ reqwest = { version = "0.11.9", features = ["json"] }
rocket = { version = "0.5.0-rc.3", features = ["json"] }
thiserror = "1.0.31"
[build-dependencies]
vergen = { version = "8.2.1", default-features = false, features = ["build", "git", "gitcl"] }
[dev-dependencies]
assert_float_eq = "1.1.3"
assert_matches = "1.5.0"
@ -42,7 +45,8 @@ Currently supported metrics are:
* O concentration (per hour, from Luchtmeetnet)
* Particulate matter (PM10) concentration (per hour, from Luchtmeetnet)
* Pollen (per hour, from Buienradar)
* Pollen/air quality index (per hour, from Buienradar)
* Pollen/air quality index (per hour, combined from Buienradar and
Luchtmeetnet)
* Precipitation (per 5 minutes, from Buienradar)
* UV index (per day, from Buienradar)

View File

@ -11,7 +11,8 @@ Currently supported metrics are:
* O₃ concentration (per hour, from [Luchtmeetnet])
* Particulate matter (PM10) concentration (per hour, from [Luchtmeetnet])
* Pollen (per hour, from [Buienradar])
* Pollen/air quality index (per hour, from [Buienradar])
* Pollen/air quality index (per hour, combined from [Buienradar] and
[Luchtmeetnet])
* Precipitation (per 5 minutes, from [Buienradar])
* UV index (per day, from [Buienradar])
@ -216,6 +217,31 @@ an address fails or if the position is out of bounds of the map, nothing is
returned (HTTP 404). If the maps cannot/have not been downloaded or cached yet,
a service unavailable error is returned (HTTP 503).
## Version API endpoint
The `/version` API endpoint provides information of the current version and
build of the service. This can be used to check if it needs to be updated.
Again, there is no path and no query parameters, just:
```http
GET /version
```
### Version responses
The response uses the JSON format and typically looks like this:
```json
{
"version": "0.2.7",
"timestamp": "2023-05-29T13:34:34.701323159Z",
"git_sha": "bb5962d",
"git_timestamp": "2023-05-29T15:32:17.000000000+02:00"
}
```
(Build and git information in example output may be out of date.)
## License
Sinoptik is licensed under the MIT license (see the `LICENSE` file or

9
build.rs Normal file
View File

@ -0,0 +1,9 @@
use std::error::Error;
use vergen::EmitBuilder;
fn main() -> Result<(), Box<dyn Error>> {
// Generate the `cargo:` instructions to fill the appropriate environment variables.
EmitBuilder::builder().all_build().all_git().emit()?;
Ok(())
}

View File

@ -21,6 +21,7 @@ use rocket::fairing::AdHoc;
use rocket::http::Status;
use rocket::response::Responder;
use rocket::serde::json::Json;
use rocket::serde::Serialize;
use rocket::{get, routes, Build, Request, Rocket, State};
use self::forecast::{forecast, Forecast, Metric};
@ -84,13 +85,41 @@ impl<'r, 'o: 'r> Responder<'r, 'o> for Error {
}
}
/// Result type that defaults to [`Error`] as the default error type.
pub(crate) type Result<T, E = Error> = std::result::Result<T, E>;
#[derive(Responder)]
#[response(content_type = "image/png")]
struct PngImageData(Vec<u8>);
/// Result type that defaults to [`Error`] as the default error type.
pub(crate) type Result<T, E = Error> = std::result::Result<T, E>;
/// The version information as JSON response.
#[derive(Debug, Serialize)]
#[serde(crate = "rocket::serde")]
struct VersionInfo {
/// The version of the build.
version: String,
/// The timestamp of the build.
timestamp: String,
/// The (most recent) git SHA used for the build.
git_sha: String,
/// The timestamp of the last git commit used for the build.
git_timestamp: String,
}
impl VersionInfo {
/// Retrieves the version information from the environment variables.
fn new() -> Self {
Self {
version: String::from(env!("CARGO_PKG_VERSION")),
timestamp: String::from(env!("VERGEN_BUILD_TIMESTAMP")),
git_sha: String::from(&env!("VERGEN_GIT_SHA")[0..7]),
git_timestamp: String::from(env!("VERGEN_GIT_COMMIT_TIMESTAMP")),
}
}
}
/// Handler for retrieving the forecast for an address.
#[get("/forecast?<address>&<metrics>")]
async fn forecast_address(
@ -150,6 +179,12 @@ async fn map_geo(
image_data.map(PngImageData)
}
/// Returns the version information.
#[get("/version", format = "application/json")]
async fn version() -> Result<Json<VersionInfo>> {
Ok(Json(VersionInfo::new()))
}
/// Sets up Rocket.
fn rocket(maps_handle: MapsHandle) -> Rocket<Build> {
let maps_refresher = maps::run(Arc::clone(&maps_handle));
@ -157,7 +192,13 @@ fn rocket(maps_handle: MapsHandle) -> Rocket<Build> {
rocket::build()
.mount(
"/",
routes![forecast_address, forecast_geo, map_address, map_geo],
routes![
forecast_address,
forecast_geo,
map_address,
map_geo,
version
],
)
.manage(maps_handle)
.attach(AdHoc::on_liftoff("Maps refresher", |_| {
@ -166,6 +207,15 @@ fn rocket(maps_handle: MapsHandle) -> Rocket<Build> {
let _refresher = rocket::tokio::spawn(maps_refresher);
})
}))
.attach(AdHoc::on_liftoff("Version", |_| {
Box::pin(async move {
let name = env!("CARGO_PKG_NAME");
let version = env!("CARGO_PKG_VERSION");
let git_sha = &env!("VERGEN_GIT_SHA")[0..7];
println!("🌁 Started {name} v{version} (git @{git_sha})");
})
}))
}
/// Sets up Rocket and the maps cache refresher task.
@ -213,8 +263,8 @@ mod tests {
let response = client.get("/forecast?address=eindhoven").dispatch();
assert_eq!(response.status(), Status::Ok);
let json = response.into_json::<JsonValue>().expect("Not valid JSON");
assert_float_absolute_eq!(json["lat"].as_f64().unwrap(), 51.4392648, 1e-8);
assert_float_absolute_eq!(json["lon"].as_f64().unwrap(), 5.478633, 1e-8);
assert_float_absolute_eq!(json["lat"].as_f64().unwrap(), 51.448557, 1e-5);
assert_float_absolute_eq!(json["lon"].as_f64().unwrap(), 5.450123, 1e-5);
assert_matches!(json["time"], JsonValue::Number(_));
assert_matches!(json.get("AQI"), None);
assert_matches!(json.get("NO2"), None);
@ -231,8 +281,8 @@ mod tests {
.dispatch();
assert_eq!(response.status(), Status::Ok);
let json = response.into_json::<JsonValue>().expect("Not valid JSON");
assert_float_absolute_eq!(json["lat"].as_f64().unwrap(), 51.4392648, 1e-8);
assert_float_absolute_eq!(json["lon"].as_f64().unwrap(), 5.478633, 1e-8);
assert_float_absolute_eq!(json["lat"].as_f64().unwrap(), 51.448557, 1e-5);
assert_float_absolute_eq!(json["lon"].as_f64().unwrap(), 5.450123, 1e-5);
assert_matches!(json["time"], JsonValue::Number(_));
assert_matches!(json.get("AQI"), Some(JsonValue::Array(_)));
assert_matches!(json.get("NO2"), Some(JsonValue::Array(_)));
@ -316,7 +366,7 @@ mod tests {
// No metric selected, don't know which map to show?
let response = client.get("/map?address=eindhoven").dispatch();
assert_eq!(response.status(), Status::NotFound);
assert_eq!(response.status(), Status::UnprocessableEntity);
}
#[test]
@ -325,10 +375,6 @@ mod tests {
let maps_handle_clone = Arc::clone(&maps_handle);
let client = Client::tracked(rocket(maps_handle)).expect("Not a valid Rocket instance");
// No metric passed, don't know which map to show?
let response = client.get("/map?lat=51.4&lon=5.5").dispatch();
assert_eq!(response.status(), Status::NotFound);
// No maps available yet.
let response = client.get("/map?lat=51.4&lon=5.5&metric=pollen").dispatch();
assert_eq!(response.status(), Status::ServiceUnavailable);
@ -345,8 +391,12 @@ mod tests {
assert_eq!(response.status(), Status::Ok);
assert_eq!(response.content_type(), Some(ContentType::PNG));
// ... but not if it is out of bounds.
let response = client.get("/map?lat=0.0&lon=0.0&metric=pollen").dispatch();
assert_eq!(response.status(), Status::NotFound);
// No metric passed, don't know which map to show?
let response = client.get("/map?lat=51.4&lon=5.5").dispatch();
assert_eq!(response.status(), Status::NotFound);
assert_eq!(response.status(), Status::UnprocessableEntity);
}
}

View File

@ -8,7 +8,7 @@ use std::f64::consts::PI;
use std::sync::{Arc, Mutex};
use chrono::serde::ts_seconds;
use chrono::{DateTime, Duration, NaiveDateTime, Utc};
use chrono::{DateTime, Duration, NaiveDateTime, TimeZone, Utc};
use image::{
DynamicImage, GenericImage, GenericImageView, ImageError, ImageFormat, Pixel, Rgb, Rgba,
};
@ -78,16 +78,16 @@ type MapKeyHistogram = HashMap<Rgb<u8>, u32>;
/// Note that the actual score starts from 1, not 0 as per this array.
#[rustfmt::skip]
const MAP_KEY: [[u8; 3]; 10] = [
[0x49, 0xDA, 0x21],
[0x30, 0xD2, 0x00],
[0xFF, 0xF8, 0x8B],
[0xFF, 0xF6, 0x42],
[0xFD, 0xBB, 0x31],
[0xFD, 0x8E, 0x24],
[0xFC, 0x10, 0x3E],
[0x97, 0x0A, 0x33],
[0xA6, 0x6D, 0xBC],
[0xB3, 0x30, 0xA1],
[0x49, 0xDA, 0x21], // #49DA21
[0x30, 0xD2, 0x00], // #30D200
[0xFF, 0xF8, 0x8B], // #FFF88B
[0xFF, 0xF6, 0x42], // #FFF642
[0xFD, 0xBB, 0x31], // #FDBB31
[0xFD, 0x8E, 0x24], // #FD8E24
[0xFC, 0x10, 0x3E], // #FC103E
[0x97, 0x0A, 0x33], // #970A33
[0xA6, 0x6D, 0xBC], // #A66DBC
[0xB3, 0x30, 0xA1], // #B330A1
];
/// The Buienradar map sample size.
@ -449,7 +449,7 @@ async fn retrieve_image(url: Url) -> Result<RetrievedMaps> {
.ok_or_else(|| Error::InvalidImagePath(path.to_owned()))?;
let timestamp = NaiveDateTime::parse_from_str(timestamp_str, "%Y%m%d%H%M")?;
DateTime::<Utc>::from_utc(timestamp, Utc)
Utc.from_utc_datetime(&timestamp)
};
let bytes = response.bytes().await?;
@ -567,7 +567,6 @@ pub(crate) async fn mark_map(
tokio::task::spawn_blocking(move || {
let maps = maps_handle.lock().expect("Maps handle lock was poisoned");
let image = match metric {
Metric::PAQI => maps.pollen_mark(position),
Metric::Pollen => maps.pollen_mark(position),
Metric::UVI => maps.uvi_mark(position),
_ => return Err(crate::Error::UnsupportedMetric(metric)),

View File

@ -109,7 +109,7 @@ pub(crate) async fn resolve_address(address: String) -> Result<Position> {
let points: Vec<Point<f64>> = osm.forward(&address)?;
points
.get(0)
.first()
.ok_or(Error::NoPositionFound)
.map(Position::from)
})

View File

@ -108,7 +108,7 @@ fn merge(
// value.
let items = pollen_samples
.into_iter()
.zip(aqi_items.into_iter())
.zip(aqi_items)
.map(|(pollen_sample, aqi_item)| {
let time = pollen_sample.time;
let value = (pollen_sample.score as f32).max(aqi_item.value);