use axum::http::StatusCode;
use chrono::{
DateTime,
Utc,
};
use pipedash_core::domain::pairing::{
display_pairing_code,
pairing_link,
};
use pipedash_core::domain::{
PairedDevice,
ServerUrl,
};
use qrcode::render::svg;
use qrcode::QrCode;
use serde::Serialize;
use crate::error::{
ApiError,
ApiResult,
AppError,
};
use crate::state::AppState;
#[derive(Debug, Serialize)]
pub struct PairingResponse {
pub code: String,
pub link: String,
pub server_url: String,
pub expires_at: DateTime<Utc>,
pub loopback: bool,
pub qr_svg: String,
}
#[derive(Debug, Serialize)]
pub struct DeviceResponse {
pub id: String,
pub name: String,
pub platform: String,
pub created_at: DateTime<Utc>,
pub last_seen_at: Option<DateTime<Utc>>,
pub current: bool,
pub removable: bool,
pub push: bool,
}
pub fn device_response(device: PairedDevice, current: Option<&str>, push: bool) -> DeviceResponse {
let current = current == Some(device.id.as_str());
DeviceResponse {
removable: !current && !crate::demo::enabled(),
current,
push,
id: device.id,
name: device.name,
platform: device.platform,
created_at: device.created_at,
last_seen_at: device.last_seen_at,
}
}
pub fn create_pairing(state: &AppState, server_url: &str) -> ApiResult<PairingResponse> {
let server = ServerUrl::parse(server_url).map_err(|reason| {
AppError::new(
StatusCode::UNPROCESSABLE_ENTITY,
ApiError::new(
"INVALID_SERVER_URL",
format!("Invalid server address: {reason}"),
),
)
})?;
let (code, expires_at) = state.pairing_codes.issue()?;
let link = pairing_link(&server.url, &code);
let qr_svg = QrCode::new(link.as_bytes())
.map_err(|error| AppError::internal(error.to_string()))?
.render::<svg::Color>()
.min_dimensions(240, 240)
.dark_color(svg::Color("#000000"))
.light_color(svg::Color("#ffffff"))
.build();
Ok(PairingResponse {
code: display_pairing_code(&code),
loopback: server.is_loopback(),
server_url: server.url,
link,
expires_at,
qr_svg,
})
}