feat: add remote, with rss, cache, discord, and zulip
This commit is contained in:
parent
50ec63a474
commit
83baab68e0
32 changed files with 6615 additions and 40 deletions
116
remote/cache-service/src/api.rs
Normal file
116
remote/cache-service/src/api.rs
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use axum::extract::{Query, State};
|
||||
use axum::http::{HeaderMap, StatusCode};
|
||||
use axum::Json;
|
||||
use tokio::sync::Mutex;
|
||||
use tracing::{error, info};
|
||||
|
||||
use crate::db;
|
||||
use crate::types::{DoorStatus, InboundWebhook, OutboundPayload, WebhookTarget};
|
||||
use crate::webhook;
|
||||
|
||||
pub struct AppState {
|
||||
pub db: Arc<Mutex<rusqlite::Connection>>,
|
||||
pub client: reqwest::Client,
|
||||
pub inbound_api_key: String,
|
||||
pub webhooks: Vec<WebhookTarget>,
|
||||
pub retry_attempts: u32,
|
||||
pub retry_base_delay_secs: u64,
|
||||
}
|
||||
|
||||
fn unix_now() -> u64 {
|
||||
std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs()
|
||||
}
|
||||
|
||||
fn validate_bearer(headers: &HeaderMap, expected: &str) -> bool {
|
||||
headers
|
||||
.get("authorization")
|
||||
.and_then(|v| v.to_str().ok())
|
||||
.map(|v| v.strip_prefix("Bearer ").unwrap_or("") == expected)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub async fn post_webhook(
|
||||
State(state): State<Arc<AppState>>,
|
||||
headers: HeaderMap,
|
||||
Json(body): Json<InboundWebhook>,
|
||||
) -> StatusCode {
|
||||
if !validate_bearer(&headers, &state.inbound_api_key) {
|
||||
return StatusCode::UNAUTHORIZED;
|
||||
}
|
||||
|
||||
let Some(status) = DoorStatus::from_str(&body.status) else {
|
||||
return StatusCode::BAD_REQUEST;
|
||||
};
|
||||
|
||||
let now = unix_now();
|
||||
{
|
||||
let conn = state.db.lock().await;
|
||||
if let Err(e) = db::update_state(&conn, status, body.timestamp, now) {
|
||||
error!(error = %e, "failed to update state from webhook");
|
||||
return StatusCode::INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
info!(status = status.as_str(), timestamp = body.timestamp, "state updated via webhook");
|
||||
|
||||
webhook::forward(
|
||||
&state.client,
|
||||
&state.webhooks,
|
||||
&OutboundPayload {
|
||||
status: status.as_str().to_string(),
|
||||
timestamp: body.timestamp,
|
||||
},
|
||||
state.retry_attempts,
|
||||
state.retry_base_delay_secs,
|
||||
)
|
||||
.await;
|
||||
|
||||
StatusCode::OK
|
||||
}
|
||||
|
||||
pub async fn get_status(State(state): State<Arc<AppState>>) -> Result<Json<serde_json::Value>, StatusCode> {
|
||||
let conn = state.db.lock().await;
|
||||
match db::get_status(&conn) {
|
||||
Ok(status) => Ok(Json(serde_json::to_value(status).unwrap())),
|
||||
Err(e) => {
|
||||
error!(error = %e, "failed to get status");
|
||||
Err(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_info(State(state): State<Arc<AppState>>) -> Result<Json<serde_json::Value>, StatusCode> {
|
||||
let conn = state.db.lock().await;
|
||||
match db::get_pi_info(&conn) {
|
||||
Ok(info) => Ok(Json(info)),
|
||||
Err(e) => {
|
||||
error!(error = %e, "failed to get pi info");
|
||||
Err(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
pub struct HistoryQuery {
|
||||
pub limit: Option<u32>,
|
||||
}
|
||||
|
||||
pub async fn get_history(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Query(query): Query<HistoryQuery>,
|
||||
) -> Result<Json<Vec<crate::types::HistoryEntry>>, StatusCode> {
|
||||
let limit = query.limit.unwrap_or(50);
|
||||
let conn = state.db.lock().await;
|
||||
match db::get_history(&conn, limit) {
|
||||
Ok(entries) => Ok(Json(entries)),
|
||||
Err(e) => {
|
||||
error!(error = %e, "failed to get history");
|
||||
Err(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue