diff --git a/services/reports/src/constants.rs b/services/reports/src/constants.rs --- a/services/reports/src/constants.rs +++ b/services/reports/src/constants.rs @@ -1,2 +1,2 @@ -pub const REPORT_LIST_PAGE_SIZE: i32 = 20; +pub const REPORT_LIST_DEFAULT_PAGE_SIZE: u32 = 20; pub const REQUEST_BODY_JSON_SIZE_LIMIT: usize = 10 * 1024 * 1024; // 10MB diff --git a/services/reports/src/database/client.rs b/services/reports/src/database/client.rs --- a/services/reports/src/database/client.rs +++ b/services/reports/src/database/client.rs @@ -1,7 +1,9 @@ +use aws_sdk_dynamodb::types::AttributeValue; use comm_services_lib::database::{ self, batch_operations::ExponentialBackoffConfig, }; +use crate::constants::REPORT_LIST_DEFAULT_PAGE_SIZE; use crate::report_types::ReportID; use super::constants::*; @@ -48,6 +50,56 @@ .map_err(database::Error::from) } + /// Performs a scan operation to get reports, returns 20 items and a cursor + /// that can be used to get next 20 items + pub async fn scan_reports( + &self, + cusror: Option, + page_size: Option, + ) -> Result { + let query = self + .ddb + .scan() + .table_name(TABLE_NAME) + .limit(page_size.unwrap_or(REPORT_LIST_DEFAULT_PAGE_SIZE) as i32); + + let request = if let Some(last_evaluated_item) = cusror { + query.exclusive_start_key( + ATTR_REPORT_ID, + AttributeValue::S(last_evaluated_item), + ) + } else { + query + }; + + let output = request + .send() + .await + .map_err(|err| database::Error::AwsSdk(err.into()))?; + + let last_evaluated_report = output + .last_evaluated_key + .map(|mut attrs| ReportID::try_from(attrs.remove(ATTR_REPORT_ID))) + .transpose()?; + + let Some(items) = output.items else { + return Ok(ReportsPage { + reports: Vec::new(), + last_evaluated_report, + }); + }; + + let reports = items + .into_iter() + .map(ReportItem::try_from) + .collect::, _>>()?; + + Ok(ReportsPage { + reports, + last_evaluated_report, + }) + } + /// Saves multiple reports to DB in batch pub async fn save_reports( &self, diff --git a/services/reports/src/http/handlers.rs b/services/reports/src/http/handlers.rs --- a/services/reports/src/http/handlers.rs +++ b/services/reports/src/http/handlers.rs @@ -37,6 +37,25 @@ Ok(response) } +#[derive(Debug, Deserialize)] +struct QueryOptions { + cursor: Option, + page_size: Option, + // there can be more options here in the future + // e.g. filter by platform, report type, user, etc. +} + +#[get("")] +async fn query_reports( + query: web::Query, + service: ReportsService, +) -> actix_web::Result { + let QueryOptions { cursor, page_size } = query.into_inner(); + let page = service.list_reports(cursor, page_size).await?; + let response = HttpResponse::Ok().json(page); + Ok(response) +} + #[get("/{report_id}")] async fn get_single_report( path: web::Path, diff --git a/services/reports/src/http/mod.rs b/services/reports/src/http/mod.rs --- a/services/reports/src/http/mod.rs +++ b/services/reports/src/http/mod.rs @@ -37,6 +37,7 @@ .service( web::scope("/reports") .service(handlers::post_reports) + .service(handlers::query_reports) .service(handlers::get_single_report) .service(handlers::redux_devtools_import), ) diff --git a/services/reports/src/service.rs b/services/reports/src/service.rs --- a/services/reports/src/service.rs +++ b/services/reports/src/service.rs @@ -12,7 +12,10 @@ use tracing::error; use crate::{ - database::{client::DatabaseClient, item::ReportItem}, + database::{ + client::{DatabaseClient, ReportsPage}, + item::ReportItem, + }, report_types::{ReportID, ReportInput, ReportOutput, ReportType}, }; @@ -144,6 +147,15 @@ .map_err(ReportsServiceError::SerdeError)?; Ok(Some(redux_devtools_payload)) } + + pub async fn list_reports( + &self, + cursor: Option, + page_size: Option, + ) -> ServiceResult { + let page = self.db.scan_reports(cursor, page_size).await?; + Ok(page) + } } impl FromRequest for ReportsService {