diff --git a/services/reports/src/database/item.rs b/services/reports/src/database/item.rs --- a/services/reports/src/database/item.rs +++ b/services/reports/src/database/item.rs @@ -213,6 +213,19 @@ ) -> Result<(), BlobServiceError> { todo!() } + + /// Fetches report content bytes + pub async fn fetch_bytes( + self, + blob_client: &BlobServiceClient, + ) -> Result, BlobServiceError> { + match self { + ReportContent::Database(data) => Ok(data), + ReportContent::Blob(BlobInfo { blob_hash, .. }) => { + todo!() + } + } + } } // DB conversions for report types 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 @@ -1,6 +1,8 @@ -use actix_web::{post, web, HttpResponse}; +use actix_web::{get, post, web, HttpResponse}; use serde::Deserialize; +use super::NotFoundHandler; + use crate::report_types::ReportInput; use crate::service::ReportsService; @@ -21,7 +23,7 @@ } } -#[post("/reports")] +#[post("")] async fn post_reports( payload: web::Json, service: ReportsService, @@ -33,3 +35,17 @@ let response = HttpResponse::Created().json(json!({ "reportIDs": ids })); Ok(response) } + +#[get("/{report_id}")] +async fn get_single_report( + path: web::Path, + service: ReportsService, +) -> actix_web::Result { + let report_id = path.into_inner(); + let report = service + .get_report(report_id.into()) + .await? + .unwrap_or_404()?; + let response = HttpResponse::Ok().json(report); + Ok(response) +} 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 @@ -1,6 +1,6 @@ use actix_web::error::{ - ErrorBadRequest, ErrorInternalServerError, ErrorServiceUnavailable, - ErrorUnsupportedMediaType, + ErrorBadRequest, ErrorInternalServerError, ErrorNotFound, + ErrorServiceUnavailable, ErrorUnsupportedMediaType, }; use actix_web::{web, App, HttpResponse, HttpServer, ResponseError}; use anyhow::Result; @@ -34,7 +34,11 @@ .wrap(cors_config(CONFIG.is_dev())) // Health endpoint for load balancers checks .route("/health", web::get().to(HttpResponse::Ok)) - .service(handlers::post_reports) + .service( + web::scope("/reports") + .service(handlers::post_reports) + .service(handlers::get_single_report), + ) }) .bind(("0.0.0.0", CONFIG.http_port))? .run() @@ -100,3 +104,14 @@ .status_code() } } + +trait NotFoundHandler { + /// Returns `Ok(T)` if `self` is `Some(T)`, + /// otherwise returns a `404 Not Found` error. + fn unwrap_or_404(self) -> actix_web::Result; +} +impl NotFoundHandler for Option { + fn unwrap_or_404(self) -> actix_web::Result { + self.ok_or_else(|| ErrorNotFound("not found")) + } +} 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 @@ -10,7 +10,7 @@ use crate::{ database::{client::DatabaseClient, item::ReportItem}, - report_types::{ReportID, ReportInput}, + report_types::{ReportID, ReportInput, ReportOutput}, }; #[derive(Debug, Display, Error, From)] @@ -94,6 +94,37 @@ self.db.save_reports(items).await?; Ok(ids) } + + pub async fn get_report( + &self, + report_id: ReportID, + ) -> ServiceResult> { + let Some(report_item) = self.db.get_report(&report_id).await? else { + return Ok(None); + }; + let ReportItem { + user_id, + report_type, + platform, + creation_time, + content, + .. + } = report_item; + + let report_data = content.fetch_bytes(&self.blob_client).await?; + let report_json = serde_json::from_slice(report_data.as_slice()) + .map_err(ReportsServiceError::SerdeError)?; + + let output = ReportOutput { + id: report_id, + user_id, + platform, + report_type, + creation_time, + content: report_json, + }; + Ok(Some(output)) + } } impl FromRequest for ReportsService {