afvalcalendar: Host enschede afvalcalendar

pull/110/head
Tristan Daniël Maat 2024-04-15 02:58:09 +02:00
parent 0d43b5177d
commit 8f178f776e
Signed by: tlater
GPG Key ID: 49670FD774E43268
10 changed files with 1651 additions and 0 deletions

View File

@ -14,6 +14,7 @@
"${modulesPath}/profiles/minimal.nix"
(import ../modules)
./services/afvalcalendar.nix
./services/backups.nix
./services/battery-manager.nix
./services/conduit.nix

View File

@ -0,0 +1,67 @@
{
pkgs,
config,
...
}: {
systemd.services.afvalcalendar = {
description = "Enschede afvalcalendar -> ical converter";
wantedBy = ["multi-user.target"];
after = ["network.target"];
script = ''
${pkgs.local.afvalcalendar}/bin/afvalcalendar > /srv/afvalcalendar/afvalcalendar.ical
'';
startAt = "daily";
serviceConfig = {
DynamicUser = true;
ProtectHome = true; # Override the default (read-only)
PrivateDevices = true;
PrivateIPC = true;
PrivateUsers = true;
ProtectHostname = true;
ProtectClock = true;
ProtectKernelTunables = true;
ProtectKernelModules = true;
ProtectKernelLogs = true;
ProtectControlGroups = true;
RestrictAddressFamilies = ["AF_UNIX" "AF_INET" "AF_INET6"];
RestrictNamespaces = true;
LockPersonality = true;
MemoryDenyWriteExecute = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
SystemCallArchitectures = "native";
SystemCallFilter = ["@system-service" "~@privileged @resources @setuid @keyring"];
Umask = 0002;
SupplementaryGroups = "afvalcalendar-hosting";
ReadWritePaths = "/srv/afvalcalendar";
};
};
services.nginx.virtualHosts."afvalcalendar.${config.services.nginx.domain}" = {
forceSSL = true;
enableACME = true;
enableHSTS = true;
root = "/srv/afvalcalendar";
};
users.groups.afvalcalendar-hosting = {};
systemd.tmpfiles.settings."10-afvalcalendar" = {
"/srv/afvalcalendar".d = {
user = "nginx";
group = "afvalcalendar-hosting";
mode = "0775";
};
"/srv/afvalcalendar/afvalcalendar.ical".f = {
user = "nginx";
group = "afvalcalendar-hosting";
mode = "0775";
};
};
}

1430
pkgs/afvalcalendar/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,16 @@
[package]
name = "afvalcalendar"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
icalendar = "0.16.0"
serde = { version = "1.0.195", features = ["derive"] }
tokio = { version = "1.35.1", features = ["macros", "rt-multi-thread"] }
reqwest = { version = "0.11", features = ["cookies", "json"] }
chrono = { version = "0.4.34", features = ["serde"] }
serde_json = "1.0.114"
serde_repr = "0.1.18"
hostname = "0.3.1"

View File

@ -0,0 +1,20 @@
{
pkgs,
rustPlatform,
...
}:
rustPlatform.buildRustPackage {
pname = "afvalcalendar";
version = "0.1.0";
src = ./.;
nativeBuildInputs = with pkgs; [
pkg-config
];
buildInputs = with pkgs; [
openssl
];
cargoHash = "sha256-JXx6aUKdKbUTBCwlBw5i1hZy8ofCfSrhLCwFzqdA8cI=";
}

View File

@ -0,0 +1,43 @@
use chrono::{Duration, NaiveDate};
use icalendar::{Alarm, Calendar, Component, Event, EventLike, Property};
use crate::trash::TrashType;
pub(crate) fn calendar_from_pickup_dates(dates: Vec<(TrashType, NaiveDate)>) -> Calendar {
let mut ical = Calendar::new();
ical.name("Twente Milieu Afvalkalender");
let events = dates.iter().map(|date| {
let description = match date.0 {
TrashType::Grey => "Restafval wordt opgehaald",
TrashType::Green => "GFT wordt opgehaald",
TrashType::Paper => "Papier wordt opgehaald",
TrashType::Packages => "Verpakkingen worden opgehaald",
};
let color = Property::new(
"COLOR",
match date.0 {
TrashType::Grey => "darkgray",
TrashType::Green => "darkgreen",
TrashType::Paper => "royalblue",
TrashType::Packages => "darkorange",
},
);
let reminder = Alarm::display(description, -Duration::hours(5));
Event::new()
.all_day(date.1)
.summary(description)
.append_property(color)
.alarm(reminder)
.done()
});
for event in events {
ical.push(event);
}
ical.done()
}

View File

@ -0,0 +1,10 @@
mod calendar;
mod trash;
#[tokio::main]
async fn main() {
let dates = trash::get_pickup_dates().await.unwrap();
let calendar = calendar::calendar_from_pickup_dates(dates);
calendar.print().unwrap();
}

View File

@ -0,0 +1,59 @@
use chrono::{Months, NaiveDate, NaiveDateTime, Utc};
use serde::Deserialize;
use serde_repr::Deserialize_repr;
#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
struct CalendarAPIDatum {
pickup_dates: Vec<NaiveDateTime>,
pickup_type: TrashType,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
struct CalendarAPIResponse {
data_list: Vec<CalendarAPIDatum>,
}
#[derive(Copy, Clone, Deserialize_repr, Debug)]
#[repr(u8)]
pub(crate) enum TrashType {
Grey = 0,
Green = 1,
Paper = 2,
Packages = 10,
}
pub(crate) async fn get_pickup_dates() -> Result<Vec<(TrashType, NaiveDate)>, reqwest::Error> {
let today = Utc::now().date_naive();
let next_month = (today + Months::new(1)).to_string();
let today = today.to_string();
let client = reqwest::Client::new();
let params = [
("companyCode", "8d97bb56-5afd-4cbc-a651-b4f7314264b4"),
("uniqueAddressID", "1300002485"),
("startDate", &today),
("endDate", &next_month),
];
let calendar = client
.post("https://twentemilieuapi.ximmio.com/api/GetCalendar")
.form(&params)
.send()
.await?
.json::<CalendarAPIResponse>()
.await?;
Ok(calendar
.data_list
.iter()
.flat_map(|datum| {
datum
.pickup_dates
.iter()
.map(|date| (datum.pickup_type, NaiveDate::from(*date)))
})
.collect::<Vec<(TrashType, NaiveDate)>>())
}

View File

@ -0,0 +1,4 @@
POST https://twentemilieuapi.ximmio.com/api/GetCalendar
Content-Type: application/x-www-form-urlencoded
companyCode=8d97bb56-5afd-4cbc-a651-b4f7314264b4&uniqueAddressID=1300002485&startDate=2024-02-01&endDate=2024-02-29

View File

@ -10,6 +10,7 @@ in
prometheus-fail2ban-exporter = callPackage ./prometheus/fail2ban-exporter.nix {
sources = pkgs.callPackage ./_sources_pkgs/generated.nix {};
};
afvalcalendar = callPackage ./afvalcalendar {};
}
// (
# Add nextcloud apps