mirror of
https://github.com/rzuasti/oott.git
synced 2026-07-08 19:21:54 +02:00
Add "go to last page" and page count to notifications and devices lists
The list endpoints now return a total count alongside the page so the
front-end can show how many pages exist and offer a last-page jump.
Backend: add count(is_new) and count_devices(...) (sharing a WHERE-builder
with list_devices so page and count can't drift), wrap both list responses
in {items, total_count} structs, and register them with utoipa.
Front-end: parse the wrapper shape (dropping the fetch-one-extra trick),
add a Last-page button and a responsive "Page X of Y" / "X / Y" label to
the shared PaginationBar, and track the total in both lists. Notifications
re-sync the count on every fetch and decrement it locally on mark-read/
unread removals so the count stays accurate without a re-fetch.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
3cb104e364
commit
844d7d189c
+168
-45
@@ -30,6 +30,53 @@ fn resolve_sort_direction(sort_order: Option<&str>) -> &'static str {
|
||||
}
|
||||
}
|
||||
|
||||
// Builds the shared `WHERE` clause (and its bound parameters) used by both
|
||||
// `list_devices` and `count_devices`, so the page and its total count always
|
||||
// apply the exact same filters. The returned clause starts with "WHERE 1=1" so
|
||||
// callers can append further SQL (ordering, paging) unconditionally.
|
||||
fn build_device_filters(
|
||||
is_registered: Option<bool>,
|
||||
last_seen_from: Option<DateTime<Utc>>,
|
||||
last_seen_to: Option<DateTime<Utc>>,
|
||||
owner: Option<String>,
|
||||
device_type: Option<String>,
|
||||
vendor: Option<String>,
|
||||
) -> (String, Vec<rusqlite::types::Value>) {
|
||||
let mut clause = "WHERE 1=1 ".to_string();
|
||||
let mut params: Vec<rusqlite::types::Value> = Vec::new();
|
||||
if let Some(is_registered) = is_registered {
|
||||
debug!("Adding filter is_registered={}", is_registered);
|
||||
clause.push_str("AND is_registered=? ");
|
||||
params.push(is_registered.into());
|
||||
};
|
||||
if let Some(last_seen_from) = last_seen_from {
|
||||
debug!("Adding filter last_seen>={}", last_seen_from.to_rfc3339());
|
||||
clause.push_str("AND last_seen>=? ");
|
||||
params.push(last_seen_from.to_rfc3339().into());
|
||||
};
|
||||
if let Some(last_seen_to) = last_seen_to {
|
||||
debug!("Adding filter last_seen<={}", last_seen_to.to_rfc3339());
|
||||
clause.push_str("AND last_seen<=? ");
|
||||
params.push(last_seen_to.to_rfc3339().into());
|
||||
};
|
||||
if let Some(owner) = owner {
|
||||
debug!("Adding filter owner={}", owner);
|
||||
clause.push_str("AND owner LIKE ? ");
|
||||
params.push(format!("%{}%", owner).into());
|
||||
};
|
||||
if let Some(device_type) = device_type {
|
||||
debug!("Adding filter device_type={}", device_type);
|
||||
clause.push_str("AND device_type=? ");
|
||||
params.push(device_type.into());
|
||||
}
|
||||
if let Some(vendor) = vendor {
|
||||
debug!("Adding filter vendor={}", vendor);
|
||||
clause.push_str("AND vendor=? ");
|
||||
params.push(vendor.into());
|
||||
}
|
||||
(clause, params)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn list_devices(
|
||||
is_registered: Option<bool>,
|
||||
@@ -47,38 +94,18 @@ pub fn list_devices(
|
||||
let conn = db::get_db_connection();
|
||||
|
||||
// Prepare SQL and parameters
|
||||
let mut sql_statement = "SELECT mac_address, ipv4_address, vendor, last_seen, is_registered, owner, device_type, name FROM devices WHERE 1=1 ".to_string();
|
||||
let mut params: Vec<rusqlite::types::Value> = Vec::new();
|
||||
if let Some(is_registered) = is_registered {
|
||||
debug!("Adding filter is_registered={}", is_registered);
|
||||
sql_statement.push_str("AND is_registered=? ");
|
||||
params.push(is_registered.into());
|
||||
};
|
||||
if let Some(last_seen_from) = last_seen_from {
|
||||
debug!("Adding filter last_seen>={}", last_seen_from.to_rfc3339());
|
||||
sql_statement.push_str("AND last_seen>=? ");
|
||||
params.push(last_seen_from.to_rfc3339().into());
|
||||
};
|
||||
if let Some(last_seen_to) = last_seen_to {
|
||||
debug!("Adding filter last_seen<={}", last_seen_to.to_rfc3339());
|
||||
sql_statement.push_str("AND last_seen<=? ");
|
||||
params.push(last_seen_to.to_rfc3339().into());
|
||||
};
|
||||
if let Some(owner) = owner {
|
||||
debug!("Adding filter owner={}", owner);
|
||||
sql_statement.push_str("AND owner LIKE ? ");
|
||||
params.push(format!("%{}%", owner).into());
|
||||
};
|
||||
if let Some(device_type) = device_type {
|
||||
debug!("Adding filter device_type={}", device_type);
|
||||
sql_statement.push_str("AND device_type=? ");
|
||||
params.push(device_type.into());
|
||||
}
|
||||
if let Some(vendor) = vendor {
|
||||
debug!("Adding filter vendor={}", vendor);
|
||||
sql_statement.push_str("AND vendor=? ");
|
||||
params.push(vendor.into());
|
||||
}
|
||||
let (filters, mut params) = build_device_filters(
|
||||
is_registered,
|
||||
last_seen_from,
|
||||
last_seen_to,
|
||||
owner,
|
||||
device_type,
|
||||
vendor,
|
||||
);
|
||||
let mut sql_statement = format!(
|
||||
"SELECT mac_address, ipv4_address, vendor, last_seen, is_registered, owner, device_type, name FROM devices {}",
|
||||
filters
|
||||
);
|
||||
|
||||
// List order — both column and direction are validated against a whitelist so user input
|
||||
// is never interpolated raw. A secondary `mac_address ASC` keeps paging deterministic when
|
||||
@@ -122,6 +149,38 @@ pub fn list_devices(
|
||||
Ok(devices)
|
||||
}
|
||||
|
||||
// Counts the devices matching the given filters, ignoring paging. Used to report
|
||||
// the total number of pages alongside a `list_devices` page.
|
||||
pub fn count_devices(
|
||||
is_registered: Option<bool>,
|
||||
last_seen_from: Option<DateTime<Utc>>,
|
||||
last_seen_to: Option<DateTime<Utc>>,
|
||||
owner: Option<String>,
|
||||
device_type: Option<String>,
|
||||
vendor: Option<String>,
|
||||
) -> Result<i64, DbError> {
|
||||
debug!("Counting devices");
|
||||
let conn = db::get_db_connection();
|
||||
|
||||
let (filters, params) = build_device_filters(
|
||||
is_registered,
|
||||
last_seen_from,
|
||||
last_seen_to,
|
||||
owner,
|
||||
device_type,
|
||||
vendor,
|
||||
);
|
||||
let sql_statement = format!("SELECT COUNT(*) FROM devices {}", filters);
|
||||
|
||||
let count: i64 = conn.query_row(
|
||||
sql_statement.as_str(),
|
||||
params_from_iter(params.iter()),
|
||||
|row| row.get(0),
|
||||
)?;
|
||||
|
||||
Ok(count)
|
||||
}
|
||||
|
||||
// Read device from its MAC address
|
||||
pub fn read(mac_address: String) -> Option<Device> {
|
||||
let conn = db::get_db_connection();
|
||||
@@ -368,7 +427,8 @@ mod tests {
|
||||
assert!(devices.len() >= 3, "There should be at least 3 devices");
|
||||
// Validate 1 device data
|
||||
let device = devices
|
||||
.iter().find(|item| item.mac_address == "bb:bb:bb:bb:bb:bb")
|
||||
.iter()
|
||||
.find(|item| item.mac_address == "bb:bb:bb:bb:bb:bb")
|
||||
.unwrap();
|
||||
|
||||
validate_device(
|
||||
@@ -386,9 +446,19 @@ mod tests {
|
||||
);
|
||||
|
||||
// List registered devices
|
||||
let devices: Vec<Device> =
|
||||
list_devices(Some(true), None, None, None, None, None, None, None, None, None)
|
||||
.unwrap();
|
||||
let devices: Vec<Device> = list_devices(
|
||||
Some(true),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(
|
||||
devices.len() >= 2,
|
||||
@@ -397,22 +467,22 @@ mod tests {
|
||||
// Device aa:aa:aa:aa:aa:aa should not be present
|
||||
assert!(
|
||||
devices
|
||||
.iter().find(|item| item.mac_address == "aa:aa:aa:aa:aa:aa")
|
||||
.iter()
|
||||
.find(|item| item.mac_address == "aa:aa:aa:aa:aa:aa")
|
||||
.is_none(),
|
||||
"Device aa:aa:aa:aa:aa:aa should not be present"
|
||||
);
|
||||
|
||||
// All devices should be registered
|
||||
assert!(
|
||||
devices
|
||||
.iter().find(|item| !item.is_registered)
|
||||
.is_none(),
|
||||
devices.iter().find(|item| !item.is_registered).is_none(),
|
||||
"There should not be any non-registered devices"
|
||||
);
|
||||
|
||||
// Validate 1 device
|
||||
let device = devices
|
||||
.iter().find(|item| item.mac_address == "bb:bb:bb:bb:bb:bb")
|
||||
.iter()
|
||||
.find(|item| item.mac_address == "bb:bb:bb:bb:bb:bb")
|
||||
.unwrap();
|
||||
|
||||
validate_device(
|
||||
@@ -479,7 +549,8 @@ mod tests {
|
||||
assert!(!devices.is_empty(), "There should be at least one device");
|
||||
assert!(
|
||||
devices
|
||||
.iter().find(|item| item.mac_address == "bb:bb:bb:bb:bb:bb")
|
||||
.iter()
|
||||
.find(|item| item.mac_address == "bb:bb:bb:bb:bb:bb")
|
||||
.is_some(),
|
||||
"Device bb:bb:bb:bb:bb:bb should be present"
|
||||
);
|
||||
@@ -526,7 +597,10 @@ mod tests {
|
||||
let macs_desc: Vec<&str> = by_mac_desc.iter().map(|d| d.mac_address.as_str()).collect();
|
||||
let mut sorted_macs_desc = macs_desc.clone();
|
||||
sorted_macs_desc.sort_by(|a, b| b.cmp(a));
|
||||
assert_eq!(macs_desc, sorted_macs_desc, "mac_address desc should be sorted reversed");
|
||||
assert_eq!(
|
||||
macs_desc, sorted_macs_desc,
|
||||
"mac_address desc should be sorted reversed"
|
||||
);
|
||||
|
||||
// Sort by owner ascending — empty-string owners (unregistered) come first lexically.
|
||||
let by_owner_asc = list_devices(
|
||||
@@ -561,9 +635,13 @@ mod tests {
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
let default_sorted = list_devices(None, None, None, None, None, None, None, None, None, None).unwrap();
|
||||
let default_sorted =
|
||||
list_devices(None, None, None, None, None, None, None, None, None, None).unwrap();
|
||||
let invalid_macs: Vec<&str> = invalid.iter().map(|d| d.mac_address.as_str()).collect();
|
||||
let default_macs: Vec<&str> = default_sorted.iter().map(|d| d.mac_address.as_str()).collect();
|
||||
let default_macs: Vec<&str> = default_sorted
|
||||
.iter()
|
||||
.map(|d| d.mac_address.as_str())
|
||||
.collect();
|
||||
assert_eq!(
|
||||
invalid_macs, default_macs,
|
||||
"invalid sort_by must fall back to the default ordering"
|
||||
@@ -618,6 +696,51 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_count_devices() {
|
||||
tests_common::setup().await;
|
||||
|
||||
// Count must match an unpaginated list, independent of paging, for both
|
||||
// no filter and a representative filter combination.
|
||||
let total = count_devices(None, None, None, None, None, None).unwrap();
|
||||
assert_eq!(
|
||||
total,
|
||||
list_devices(None, None, None, None, None, None, None, None, None, None)
|
||||
.unwrap()
|
||||
.len() as i64,
|
||||
"Count of all devices should match the unpaginated list length"
|
||||
);
|
||||
|
||||
// Paging the list must not change the count.
|
||||
let total_paged = count_devices(None, None, None, None, None, None).unwrap();
|
||||
assert_eq!(total_paged, total, "Count should ignore paging");
|
||||
|
||||
// Filtered count matches the filtered unpaginated list.
|
||||
let registered = count_devices(Some(true), None, None, None, None, None).unwrap();
|
||||
assert_eq!(
|
||||
registered,
|
||||
list_devices(
|
||||
Some(true),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.unwrap()
|
||||
.len() as i64,
|
||||
"Count of registered devices should match the filtered list length"
|
||||
);
|
||||
assert!(
|
||||
registered <= total,
|
||||
"Filtered count should not exceed the total"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_seen() {
|
||||
tests_common::setup().await;
|
||||
|
||||
@@ -62,6 +62,30 @@ pub fn list(
|
||||
Ok(notifications)
|
||||
}
|
||||
|
||||
// Counts the notifications matching the given filter, ignoring paging. Used to
|
||||
// report the total number of pages alongside a `list` page.
|
||||
pub fn count(is_new: Option<bool>) -> Result<i64, DbError> {
|
||||
debug!("Counting notifications");
|
||||
let conn = db::get_db_connection();
|
||||
|
||||
let mut sql_statement = "SELECT COUNT(*) FROM notifications WHERE 1=1".to_string();
|
||||
let mut params: Vec<rusqlite::types::Value> = Vec::new();
|
||||
|
||||
if let Some(is_new) = is_new {
|
||||
debug!("Adding filter is_new={}", is_new);
|
||||
sql_statement.push_str(" AND is_new=?");
|
||||
params.push(is_new.into());
|
||||
}
|
||||
|
||||
let count: i64 = conn.query_row(
|
||||
sql_statement.as_str(),
|
||||
params_from_iter(params.iter()),
|
||||
|row| row.get(0),
|
||||
)?;
|
||||
|
||||
Ok(count)
|
||||
}
|
||||
|
||||
pub fn insert(notification: Notification) -> Result<i64, DbError> {
|
||||
let conn = db::get_db_connection();
|
||||
let mac_address = notification.mac_address.as_deref().map(normalize_mac);
|
||||
@@ -360,10 +384,7 @@ mod tests {
|
||||
NotificationType::DeviceOnlineAfterTime,
|
||||
"Wrong notification type (should be DeviceOnlineAfterTime)"
|
||||
);
|
||||
assert!(
|
||||
inserted_notification.is_new,
|
||||
"Notification should be new"
|
||||
);
|
||||
assert!(inserted_notification.is_new, "Notification should be new");
|
||||
assert_eq!(
|
||||
inserted_notification.created_on, created_on,
|
||||
"Wrong created_on (should be ${created_on})"
|
||||
@@ -502,10 +523,7 @@ mod tests {
|
||||
NotificationType::DeviceOnlineAfterTime,
|
||||
"Wrong notification type (should be DeviceOnlineAfterTime)"
|
||||
);
|
||||
assert!(
|
||||
inserted_notification.is_new,
|
||||
"Notification should be new"
|
||||
);
|
||||
assert!(inserted_notification.is_new, "Notification should be new");
|
||||
assert_eq!(
|
||||
inserted_notification.created_on, created_on,
|
||||
"Wrong created_on (should be ${created_on})"
|
||||
@@ -576,7 +594,8 @@ mod tests {
|
||||
|
||||
// Check date of notification 1
|
||||
let notification1 = notifications
|
||||
.iter().find(|notification| notification.id == 1)
|
||||
.iter()
|
||||
.find(|notification| notification.id == 1)
|
||||
.unwrap();
|
||||
|
||||
// 2026-01-03 14:13:12 - UTC
|
||||
@@ -588,7 +607,8 @@ mod tests {
|
||||
|
||||
// Check title of notification 3
|
||||
let notification3 = notifications
|
||||
.iter().find(|notification| notification.id == 3)
|
||||
.iter()
|
||||
.find(|notification| notification.id == 3)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
@@ -599,7 +619,8 @@ mod tests {
|
||||
|
||||
// Check body of notification 5
|
||||
let notification5 = notifications
|
||||
.iter().find(|notification| notification.id == 5)
|
||||
.iter()
|
||||
.find(|notification| notification.id == 5)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
@@ -648,4 +669,44 @@ mod tests {
|
||||
"Second page should have at least 1 notification"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_count() {
|
||||
tests_common::setup().await;
|
||||
|
||||
// The count must match an unpaginated list for each filter.
|
||||
let all = count(None).unwrap();
|
||||
assert_eq!(
|
||||
all,
|
||||
list(None, None, None).unwrap().len() as i64,
|
||||
"Count of all notifications should match the unpaginated list length"
|
||||
);
|
||||
|
||||
let new = count(Some(true)).unwrap();
|
||||
assert_eq!(
|
||||
new,
|
||||
list(Some(true), None, None).unwrap().len() as i64,
|
||||
"Count of new notifications should match the unpaginated list length"
|
||||
);
|
||||
|
||||
let old = count(Some(false)).unwrap();
|
||||
assert_eq!(
|
||||
old,
|
||||
list(Some(false), None, None).unwrap().len() as i64,
|
||||
"Count of old notifications should match the unpaginated list length"
|
||||
);
|
||||
|
||||
// New + old must add up to the total, and the count must be independent
|
||||
// of paging.
|
||||
assert_eq!(
|
||||
new + old,
|
||||
all,
|
||||
"New and old counts should add up to the total"
|
||||
);
|
||||
assert_eq!(
|
||||
count(Some(true)).unwrap(),
|
||||
new,
|
||||
"Count should ignore paging parameters"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,14 @@ pub struct Device {
|
||||
pub name: Option<String>,
|
||||
}
|
||||
|
||||
// A page of devices plus the total number of devices matching the request's
|
||||
// filters, so the front-end can show how many pages exist.
|
||||
#[derive(Clone, Serialize, Deserialize, ToSchema)]
|
||||
pub struct DeviceListResponse {
|
||||
pub items: Vec<Device>,
|
||||
pub total_count: i64,
|
||||
}
|
||||
|
||||
impl Device {
|
||||
pub fn new(
|
||||
mac_address: String,
|
||||
|
||||
@@ -18,6 +18,14 @@ pub struct Notification {
|
||||
pub mac_address: Option<String>,
|
||||
}
|
||||
|
||||
// A page of notifications plus the total number of notifications matching the
|
||||
// request's filters, so the front-end can show how many pages exist.
|
||||
#[derive(Clone, Serialize, Deserialize, ToSchema)]
|
||||
pub struct NotificationListResponse {
|
||||
pub items: Vec<Notification>,
|
||||
pub total_count: i64,
|
||||
}
|
||||
|
||||
impl Notification {
|
||||
pub fn new(
|
||||
created_on: DateTime<Utc>,
|
||||
|
||||
@@ -2,8 +2,8 @@ use std::error::Error;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use crate::model::device_events::{DeviceEvent, DeviceEventScanner, DeviceEventType};
|
||||
use crate::model::devices::{Device, DeviceSummary};
|
||||
use crate::model::notifications::{Notification, NotificationType};
|
||||
use crate::model::devices::{Device, DeviceListResponse, DeviceSummary};
|
||||
use crate::model::notifications::{Notification, NotificationListResponse, NotificationType};
|
||||
use crate::settings::get_settings;
|
||||
use crate::web_server::devices::{RegisterDevicePayload, UpdateDevicePayload};
|
||||
use crate::web_server::scanner_status::{
|
||||
@@ -66,8 +66,10 @@ pub mod utils;
|
||||
),
|
||||
components(schemas(
|
||||
Device,
|
||||
DeviceListResponse,
|
||||
DeviceSummary,
|
||||
Notification,
|
||||
NotificationListResponse,
|
||||
NotificationType,
|
||||
RegisterDevicePayload,
|
||||
UpdateDevicePayload,
|
||||
|
||||
@@ -10,7 +10,7 @@ use utoipa::ToSchema;
|
||||
|
||||
use crate::{
|
||||
db,
|
||||
model::devices::{Device, DeviceSummary},
|
||||
model::devices::{Device, DeviceListResponse, DeviceSummary},
|
||||
};
|
||||
|
||||
use crate::web_server::utils;
|
||||
@@ -32,14 +32,14 @@ use crate::web_server::utils;
|
||||
("page_limit" = Option<i64>, Query, description = "Maximum number of results to return"),
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "List of devices", body = Vec<Device>),
|
||||
(status = 200, description = "List of devices", body = DeviceListResponse),
|
||||
(status = 500, description = "Internal server error"),
|
||||
),
|
||||
security(("bearer_auth" = []))
|
||||
)]
|
||||
pub async fn list(
|
||||
Query(params): Query<HashMap<String, String>>,
|
||||
) -> Result<Json<Vec<Device>>, StatusCode> {
|
||||
) -> Result<Json<DeviceListResponse>, StatusCode> {
|
||||
let is_registered: Option<bool> = utils::parse_parameter_bool(¶ms, "is_registered");
|
||||
let last_seen_from: Option<DateTime<Utc>> =
|
||||
utils::parse_parameter_date(¶ms, "last_seen_from");
|
||||
@@ -52,24 +52,41 @@ pub async fn list(
|
||||
let page_offset: Option<i64> = utils::parse_parameter_int(¶ms, "page_offset");
|
||||
let page_limit: Option<i64> = utils::parse_parameter_int(¶ms, "page_limit");
|
||||
|
||||
match db::devices::list_devices(
|
||||
let items = match db::devices::list_devices(
|
||||
is_registered,
|
||||
last_seen_from,
|
||||
last_seen_to,
|
||||
owner.clone(),
|
||||
device_type.clone(),
|
||||
vendor.clone(),
|
||||
sort_by,
|
||||
sort_order,
|
||||
page_offset,
|
||||
page_limit,
|
||||
) {
|
||||
Ok(value) => value,
|
||||
Err(err) => {
|
||||
error!("Error listing devices: {}", err);
|
||||
return Err(StatusCode::INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
};
|
||||
|
||||
let total_count = match db::devices::count_devices(
|
||||
is_registered,
|
||||
last_seen_from,
|
||||
last_seen_to,
|
||||
owner,
|
||||
device_type,
|
||||
vendor,
|
||||
sort_by,
|
||||
sort_order,
|
||||
page_offset,
|
||||
page_limit,
|
||||
) {
|
||||
Ok(value) => Ok(Json(value)),
|
||||
Ok(value) => value,
|
||||
Err(err) => {
|
||||
error!("Error listing devices: {}", err);
|
||||
Err(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
error!("Error counting devices: {}", err);
|
||||
return Err(StatusCode::INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Json(DeviceListResponse { items, total_count }))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
|
||||
@@ -8,7 +8,11 @@ use axum::{
|
||||
};
|
||||
use log::error;
|
||||
|
||||
use crate::{db, model::notifications::Notification, web_server::utils};
|
||||
use crate::{
|
||||
db,
|
||||
model::notifications::{Notification, NotificationListResponse},
|
||||
web_server::utils,
|
||||
};
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
@@ -118,23 +122,33 @@ pub async fn mark_all_as_old() -> impl IntoResponse {
|
||||
("page_limit" = Option<i64>, Query, description = "Maximum number of results to return"),
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "List of notifications", body = Vec<Notification>),
|
||||
(status = 200, description = "List of notifications", body = NotificationListResponse),
|
||||
(status = 500, description = "Internal server error"),
|
||||
),
|
||||
security(("bearer_auth" = []))
|
||||
)]
|
||||
pub async fn list(
|
||||
Query(params): Query<HashMap<String, String>>,
|
||||
) -> Result<Json<Vec<Notification>>, StatusCode> {
|
||||
) -> Result<Json<NotificationListResponse>, StatusCode> {
|
||||
let is_new: Option<bool> = utils::parse_parameter_bool(¶ms, "is_new");
|
||||
let page_offset: Option<i64> = utils::parse_parameter_int(¶ms, "page_offset");
|
||||
let page_limit: Option<i64> = utils::parse_parameter_int(¶ms, "page_limit");
|
||||
|
||||
match db::notifications::list(is_new, page_offset, page_limit) {
|
||||
Ok(value) => Ok(Json(value)),
|
||||
let items = match db::notifications::list(is_new, page_offset, page_limit) {
|
||||
Ok(value) => value,
|
||||
Err(err) => {
|
||||
error!("Error listing notifications: {}", err);
|
||||
Err(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
return Err(StatusCode::INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let total_count = match db::notifications::count(is_new) {
|
||||
Ok(value) => value,
|
||||
Err(err) => {
|
||||
error!("Error counting notifications: {}", err);
|
||||
return Err(StatusCode::INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Json(NotificationListResponse { items, total_count }))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user