rustpbx 0.3.19

A SIP PBX implementation in Rust
Documentation
use crate::models::sip_trunk::{SipTransport, SipTrunkDirection, SipTrunkStatus};
use crate::proxy::routing::RouteQueueConfig;
use sea_orm::{DatabaseConnection, DbErr, Paginator, SelectorTrait};
use serde::{Deserialize, Serialize};
use std::cmp;

#[derive(Deserialize, Default, Clone)]
pub struct LoginQuery {
    pub next: Option<String>,
}

#[derive(Deserialize, Default, Clone)]
pub struct LoginForm {
    pub identifier: String,
    pub password: String,
    pub next: Option<String>,
}

#[derive(Deserialize, Default, Clone)]
pub struct RegisterForm {
    pub email: String,
    pub username: String,
    pub password: String,
    pub confirm_password: String,
}

#[derive(Deserialize, Default, Clone)]
pub struct ForgotForm {
    pub email: String,
}

#[derive(Deserialize, Default, Clone)]
pub struct ResetForm {
    pub password: String,
    pub confirm_password: String,
}

#[derive(Deserialize, Default, Clone)]
pub struct MfaForm {
    pub code: String,
}

#[derive(Deserialize, Default, Clone)]
pub struct ExtensionPayload {
    pub extension: Option<String>,
    pub display_name: Option<String>,
    pub email: Option<String>,
    pub sip_password: Option<String>,
    pub call_forwarding_mode: Option<String>,
    pub call_forwarding_destination: Option<String>,
    pub call_forwarding_timeout: Option<i32>,
    pub notes: Option<String>,
    pub department_ids: Option<Vec<i64>>,
    pub login_disabled: Option<bool>,
    pub voicemail_disabled: Option<bool>,
    pub allow_guest_calls: Option<bool>,
}

#[derive(Deserialize, Default, Clone)]
pub struct SipTrunkForm {
    pub name: Option<String>,
    pub display_name: Option<String>,
    pub carrier: Option<String>,
    pub description: Option<String>,
    pub status: Option<SipTrunkStatus>,
    pub direction: Option<SipTrunkDirection>,
    pub sip_server: Option<String>,
    pub sip_transport: Option<SipTransport>,
    pub outbound_proxy: Option<String>,
    pub auth_username: Option<String>,
    pub auth_password: Option<String>,
    pub default_route_label: Option<String>,
    pub tenant_id: Option<i64>,
    pub clear_tenant: Option<bool>,
    pub max_cps: Option<i32>,
    pub max_concurrent: Option<i32>,
    pub max_call_duration: Option<i32>,
    pub utilisation_percent: Option<f64>,
    pub warning_threshold_percent: Option<f64>,
    pub allowed_ips: Option<String>,
    pub did_numbers: Option<String>,
    pub billing_snapshot: Option<String>,
    pub analytics: Option<String>,
    pub tags: Option<String>,
    pub incoming_from_user_prefix: Option<String>,
    pub incoming_to_user_prefix: Option<String>,
    pub metadata: Option<String>,
    pub is_active: Option<bool>,
    pub register_enabled: Option<bool>,
    pub register_expires: Option<i32>,
    pub register_extra_headers: Option<String>,
}

#[derive(Deserialize, Default, Clone)]
pub struct QueuePayload {
    pub name: Option<String>,
    pub description: Option<String>,
    pub is_active: Option<bool>,
    pub metadata: Option<String>,
    pub tags: Option<Vec<String>>,
    #[serde(default)]
    pub spec: Option<RouteQueueConfig>,
}

fn default_per_page_min() -> u32 {
    5
}
fn default_per_page_max() -> u32 {
    100
}

#[derive(Deserialize)]
pub struct ListQuery<T> {
    pub page: u32,
    pub per_page: u32,
    #[serde(default = "default_per_page_min")]
    pub per_page_min: u32,
    #[serde(default = "default_per_page_max")]
    pub per_page_max: u32,
    pub filters: Option<T>,
    #[serde(default)]
    pub sort: Option<String>,
}

impl<T: Default> Default for ListQuery<T> {
    fn default() -> Self {
        Self {
            page: 1,
            per_page: 20,
            per_page_min: default_per_page_min(),
            per_page_max: default_per_page_max(),
            filters: None,
            sort: None,
        }
    }
}

impl<T> ListQuery<T> {
    pub fn normalize(&self) -> (u64, u64) {
        let normalized_min = cmp::max(self.per_page_min, 1);
        let normalized_max = cmp::max(self.per_page_max, normalized_min);
        let clamped_per_page = cmp::min(cmp::max(self.per_page, normalized_min), normalized_max);
        let clamped_page = cmp::max(self.page, 1);
        (clamped_page as u64, clamped_per_page as u64)
    }
}

#[derive(Debug, Serialize)]
pub struct Pagination<T> {
    pub items: Vec<T>,
    pub current_page: u32,
    pub per_page: u32,
    pub total_items: u64,
    pub total_pages: u32,
    pub has_prev: bool,
    pub has_next: bool,
}

pub async fn paginate<S, F>(
    paginator: Paginator<'_, DatabaseConnection, S>,
    query: &ListQuery<F>,
) -> Result<Pagination<S::Item>, DbErr>
where
    S: SelectorTrait + Send,
    S::Item: Send,
    F: Default,
{
    let (page, per_page) = query.normalize();
    let total_items = paginator.num_items().await?;
    let total_pages_u64 = if total_items == 0 {
        1
    } else {
        cmp::max((total_items + per_page - 1) / per_page, 1)
    };

    let total_pages = cmp::max(u32::try_from(total_pages_u64).unwrap_or(u32::MAX), 1);
    let requested_page = cmp::max(page, 1);
    let max_page_index = total_pages.saturating_sub(1);
    let page_index_u32 = cmp::min(requested_page.saturating_sub(1), max_page_index as u64);
    let page_index = page_index_u32 as u64;

    let items = paginator.fetch_page(page_index).await?;

    let current_page = (page_index as u32) + 1;
    let has_prev = current_page > 1;
    let has_next = current_page < total_pages;

    Ok(Pagination {
        items,
        current_page,
        per_page: per_page as u32,
        total_items,
        total_pages,
        has_prev,
        has_next,
    })
}