1use axum::http::StatusCode;
2use chrono::{
3 DateTime,
4 Utc,
5};
6use pipedash_core::domain::pairing::{
7 display_pairing_code,
8 pairing_link,
9};
10use pipedash_core::domain::{
11 PairedDevice,
12 ServerUrl,
13};
14use qrcode::render::svg;
15use qrcode::QrCode;
16use serde::Serialize;
17
18use crate::error::{
19 ApiError,
20 ApiResult,
21 AppError,
22};
23use crate::state::AppState;
24
25#[derive(Debug, Serialize)]
26pub struct PairingResponse {
27 pub code: String,
28 pub link: String,
29 pub server_url: String,
30 pub expires_at: DateTime<Utc>,
31 pub loopback: bool,
32 pub qr_svg: String,
33}
34
35#[derive(Debug, Serialize)]
36pub struct DeviceResponse {
37 pub id: String,
38 pub name: String,
39 pub platform: String,
40 pub created_at: DateTime<Utc>,
41 pub last_seen_at: Option<DateTime<Utc>>,
42 pub current: bool,
43 pub removable: bool,
44 pub push: bool,
45}
46
47pub fn device_response(device: PairedDevice, current: Option<&str>, push: bool) -> DeviceResponse {
48 let current = current == Some(device.id.as_str());
49 DeviceResponse {
50 removable: !current && !crate::demo::enabled(),
51 current,
52 push,
53 id: device.id,
54 name: device.name,
55 platform: device.platform,
56 created_at: device.created_at,
57 last_seen_at: device.last_seen_at,
58 }
59}
60
61pub fn create_pairing(state: &AppState, server_url: &str) -> ApiResult<PairingResponse> {
62 let server = ServerUrl::parse(server_url).map_err(|reason| {
63 AppError::new(
64 StatusCode::UNPROCESSABLE_ENTITY,
65 ApiError::new(
66 "INVALID_SERVER_URL",
67 format!("Invalid server address: {reason}"),
68 ),
69 )
70 })?;
71 let (code, expires_at) = state.pairing_codes.issue()?;
72 let link = pairing_link(&server.url, &code);
73 let qr_svg = QrCode::new(link.as_bytes())
74 .map_err(|error| AppError::internal(error.to_string()))?
75 .render::<svg::Color>()
76 .min_dimensions(240, 240)
77 .dark_color(svg::Color("#000000"))
78 .light_color(svg::Color("#ffffff"))
79 .build();
80
81 Ok(PairingResponse {
82 code: display_pairing_code(&code),
83 loopback: server.is_loopback(),
84 server_url: server.url,
85 link,
86 expires_at,
87 qr_svg,
88 })
89}