mirror of
https://github.com/jhbruhn/ics-adapter.git
synced 2025-03-15 03:25:49 +00:00
initial
This commit is contained in:
commit
079dcb97bb
6 changed files with 1894 additions and 0 deletions
41
.github/workflows/release.yaml
vendored
Normal file
41
.github/workflows/release.yaml
vendored
Normal file
|
@ -0,0 +1,41 @@
|
|||
name: Create and publish a Docker image
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ['main']
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
|
||||
jobs:
|
||||
build-and-push-image:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/target
|
1693
Cargo.lock
generated
Normal file
1693
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
14
Cargo.toml
Normal file
14
Cargo.toml
Normal file
|
@ -0,0 +1,14 @@
|
|||
[package]
|
||||
name = "ics-adapter"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
warp = "0.3"
|
||||
reqwest = { version = "0.11" }
|
||||
anyhow = "1"
|
||||
icalendar = { version = "0.15", features = ["chrono-tz"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
38
Dockerfile
Normal file
38
Dockerfile
Normal file
|
@ -0,0 +1,38 @@
|
|||
FROM rust:1.70 as builder
|
||||
|
||||
RUN USER=root cargo new --bin ics-adapter
|
||||
WORKDIR ./ics-adapter
|
||||
COPY ./Cargo.toml ./Cargo.toml
|
||||
RUN cargo build --release
|
||||
RUN rm src/*.rs
|
||||
|
||||
ADD . ./
|
||||
|
||||
RUN rm ./target/release/deps/ics-adapter*
|
||||
RUN cargo build --release
|
||||
|
||||
|
||||
FROM debian:buster-slim
|
||||
ARG APP=/usr/src/app
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y ca-certificates tzdata \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
ENV TZ=Etc/UTC \
|
||||
APP_USER=appuser
|
||||
|
||||
RUN groupadd $APP_USER \
|
||||
&& useradd -g $APP_USER $APP_USER \
|
||||
&& mkdir -p ${APP}
|
||||
|
||||
COPY --from=builder /ics-adapter/target/release/ics-adapter ${APP}/ics-adapter
|
||||
|
||||
RUN chown -R $APP_USER:$APP_USER ${APP}
|
||||
|
||||
USER $APP_USER
|
||||
WORKDIR ${APP}
|
||||
|
||||
CMD ["./ics-adapter"]
|
107
src/main.rs
Normal file
107
src/main.rs
Normal file
|
@ -0,0 +1,107 @@
|
|||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use warp::{
|
||||
Filter,
|
||||
Reply,
|
||||
Rejection,
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
use anyhow::Result;
|
||||
use icalendar::{Component, Calendar, EventLike};
|
||||
use serde::Serialize;
|
||||
|
||||
fn now_timestamp_secs() -> i64 {
|
||||
let start = SystemTime::now();
|
||||
let since_the_epoch = start
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.expect("Time went backwards");
|
||||
(since_the_epoch.as_millis() / 1000).try_into().unwrap()
|
||||
}
|
||||
|
||||
async fn handler(params: HashMap<String, String>) -> Result<impl Reply, Rejection> {
|
||||
match params.get("url") {
|
||||
Some(url) => Ok(warp::reply::json(&convert(&url).await.map_err(|_| warp::reject::reject())?.entries)),
|
||||
None => Err(warp::reject::reject())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct CustomCalendar {
|
||||
entries: Vec<CustomCalendarEntry>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct CustomCalendarEntry {
|
||||
title: String,
|
||||
start: i64,
|
||||
end: i64,
|
||||
location: String,
|
||||
description: String,
|
||||
}
|
||||
|
||||
async fn convert(url: &str) -> Result<CustomCalendar> {
|
||||
let ics_text = reqwest::get(url)
|
||||
.await?
|
||||
.text()
|
||||
.await?;
|
||||
|
||||
let calendar = ics_text.parse::<Calendar>().map_err(|e| anyhow::Error::msg(e))?;
|
||||
|
||||
let mut entries = Vec::new();
|
||||
|
||||
let filter_start = now_timestamp_secs();
|
||||
let filter_end = now_timestamp_secs() + (24 * 60 * 60);
|
||||
|
||||
for event in calendar.components {
|
||||
if let Some(event) = event.as_event() {
|
||||
let Some(start) = event.get_start() else {
|
||||
println!("No start!");
|
||||
continue;
|
||||
};
|
||||
let Some(end) = event.get_end() else {
|
||||
println!("No end!");
|
||||
continue;
|
||||
};
|
||||
|
||||
let Ok(start) = convert_time(start) else {
|
||||
println!("Invalid timestamp");
|
||||
continue;
|
||||
};
|
||||
let Ok(end) = convert_time(end) else {
|
||||
println!("Invalid timestamp");
|
||||
continue;
|
||||
};
|
||||
|
||||
if start < filter_start || start > filter_end {
|
||||
continue;
|
||||
}
|
||||
entries.push(CustomCalendarEntry {
|
||||
title: event.get_summary().unwrap_or("").to_string(),
|
||||
description: event.get_description().unwrap_or("").to_string(),
|
||||
location: event.get_location().unwrap_or("").to_string(),
|
||||
start,
|
||||
end
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(CustomCalendar{entries})
|
||||
}
|
||||
|
||||
fn convert_time(dt: icalendar::DatePerhapsTime) -> Result<i64> {
|
||||
Ok(match dt {
|
||||
icalendar::DatePerhapsTime::DateTime(cdt) => cdt.try_into_utc().ok_or(anyhow::Error::msg("failed to convert to utc"))?.timestamp(),
|
||||
icalendar::DatePerhapsTime::Date(nd) => nd.and_hms_opt(0, 0, 0).unwrap().timestamp(),
|
||||
})
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let converter = warp::get()
|
||||
.and(warp::path("get"))
|
||||
.and(warp::query::<HashMap<String, String>>())
|
||||
.and_then(handler);
|
||||
|
||||
warp::serve(converter)
|
||||
.run(([0, 0, 0, 0], 3000))
|
||||
.await
|
||||
}
|
Loading…
Reference in a new issue