Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Persist a new request to the data_requests table only if the patient id, project id combination does not exist. #12

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

This file was deleted.

This file was deleted.

This file was deleted.

7 changes: 5 additions & 2 deletions migrations/20240614130457_init.up.sql
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ VALUES ('Created', 1),
('Error', 4);

CREATE TABLE IF NOT EXISTS data_requests (
id CHAR(36) PRIMARY KEY NOT NULL,
status CHAR(16) NOT NULL DEFAULT ('Created') REFERENCES request_status(Type)
id CHAR(36) PRIMARY KEY NOT NULL,
patient_id CHAR(36) NOT NULL,
project_id CHAR(36) NOT NULL,
status CHAR(16) NOT NULL DEFAULT ('Created') REFERENCES request_status(Type),
UNIQUE(patient_id, project_id)
)
63 changes: 63 additions & 0 deletions src/data_access/data_requests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
use super::models::DataRequest;
use sqlx::SqlitePool;
use tracing::debug;

pub async fn get_all(db_pool: &SqlitePool) -> Result<Vec<DataRequest>, sqlx::Error> {
let data_request = sqlx::query_as!(
DataRequest,
r#"SELECT id, patient_id, project_id, status as "status: _" FROM data_requests;"#,
)
.fetch_all(db_pool)
.await;

data_request
}

pub async fn get_by_id(db_pool: &SqlitePool, id: &str) -> Result<Option<DataRequest>, sqlx::Error> {
let data_request = sqlx::query_as!(
DataRequest,
r#"SELECT id, patient_id, project_id, status as "status: _" FROM data_requests WHERE id = $1;"#,
id
)
.fetch_optional(db_pool)
.await;

data_request
}

pub async fn get_by(
db_pool: &SqlitePool,
patient_id: &str,
project_id: &str,
) -> Result<Option<DataRequest>, sqlx::Error> {
let data_request = sqlx::query_as!(
DataRequest,
r#"SELECT id, patient_id, project_id, status as "status: _" FROM data_requests WHERE patient_id = $1 AND project_id = $2;"#,
patient_id, project_id
)
.fetch_optional(db_pool)
.await;

debug!("id: {patient_id}, project id: {project_id}, data request: {:?}", data_request.is_err());
data_request
}

pub async fn exists(db_pool: &SqlitePool, id: &str, project_id: &str) -> bool {
let data_request = get_by(db_pool, id, project_id).await.unwrap_or(None);
data_request.is_some()
}

pub async fn insert(db_pool: &SqlitePool, data_request: &DataRequest) -> Result<i64, sqlx::Error> {
let insert_query_result = sqlx::query!(
"INSERT INTO data_requests (id, patient_id, project_id, status) VALUES ($1, $2, $3, $4)",
data_request.id,
data_request.patient_id,
data_request.project_id,
data_request.status
)
.execute(db_pool)
.await
.map(|qr| qr.last_insert_rowid());

insert_query_result
}
2 changes: 2 additions & 0 deletions src/data_access/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod data_requests;
pub mod models;
36 changes: 36 additions & 0 deletions src/data_access/models.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
use fhir_sdk::r4b::resources::{Consent, Patient};
use serde::{Deserialize, Serialize};

#[derive(Clone, Default, Serialize, Deserialize, sqlx::Type)]
pub enum RequestStatus {
Created = 1,
_DataLoaded = 2,
_UpdateAvailable = 3,
#[default]
Error = 4,
}

#[derive(Clone, Default, Serialize, Deserialize, sqlx::FromRow)]
pub struct DataRequest {
pub id: String,
pub patient_id: String,
pub project_id: String,
pub status: RequestStatus,
}

impl DataRequest {
pub fn new(id: String, patient_id: String, project_id: String) -> Self {
Self {
id,
patient_id,
project_id,
status: RequestStatus::Created,
}
}
}

#[derive(Serialize, Deserialize, Clone)]
pub struct DataRequestPayload {
pub patient: Patient,
pub consent: Consent,
}
2 changes: 1 addition & 1 deletion src/fhir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use fhir_sdk::r4b::{
use reqwest::{header, Client, StatusCode, Url};
use tracing::{debug, error, warn};

use crate::{config::Auth, requests::DataRequestPayload, CONFIG};
use crate::{config::Auth, data_access::models::DataRequestPayload, CONFIG};

#[derive(Clone, Debug)]
pub struct FhirServer {
Expand Down
51 changes: 40 additions & 11 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ mod config;
mod fhir;
mod requests;
mod ttp;
mod data_access;

static CONFIG: Lazy<Config> = Lazy::new(Config::parse);
static SERVER_ADDRESS: &str = "0.0.0.0:8080";
Expand Down Expand Up @@ -132,27 +133,55 @@ async fn fetch_data(input_fhir_server: &FhirServer, output_fhir_server: &FhirSer
mod tests {
use pretty_assertions::assert_eq;
use reqwest::StatusCode;

use crate::requests::DataRequest;

async fn post_data_request() -> DataRequest {
let bytes = include_bytes!("../docs/examples/data_request.json");
let json = &serde_json::from_slice::<serde_json::Value>(bytes).unwrap();
use crate::data_access::models::DataRequest;

const BASE_API_URL: &str = "http://localhost:8080/requests";

async fn get_all_data_requests() -> Vec<DataRequest> {
let response = reqwest::Client::new()
.post(format!("http://localhost:8080/requests"))
.json(json)
.get(BASE_API_URL)
.send()
.await
.expect("POST endpoint (/requests) should give a valid response");
assert_eq!(response.status(), StatusCode::CREATED);
response.json().await.unwrap()
.expect("GET endpoint (/requests) should give a valid response");
assert_eq!(response.status(), StatusCode::OK);

let data_requests = response.json::<Vec<DataRequest>>().await.unwrap();
dbg!("number of rows in data_requests table: {}", data_requests.len());
data_requests
}

async fn post_data_request() -> DataRequest {
let data_requests = get_all_data_requests().await;
if data_requests.len() > 0 {
// NOTE: the tests always use a hard-coded patient from the
// ../docs/examples/data_request.json file, so this test is fine.
// Even if we find a single row in the data_requests table, it is
// for this one single patient only.
dbg!("a data request for this patient already exists, returning the saved one");
data_requests[0].clone()
} else {
dbg!("creating a new data request");
let bytes = include_bytes!("../docs/examples/data_request.json");
let json = &serde_json::from_slice::<serde_json::Value>(bytes).unwrap();

let response = reqwest::Client::new()
.post(BASE_API_URL)
.json(json)
.send()
.await
.expect("POST endpoint (/requests) should give a valid response");
assert_eq!(response.status(), StatusCode::CREATED);
response.json().await.unwrap()
}
}

#[tokio::test]
async fn get_data_request() {
let data_request = post_data_request().await;
let url = format!("http://localhost:8080/requests/{}", data_request.id);
dbg!("data request id: {}", &data_request.id);

let url = format!("{BASE_API_URL}/{}", data_request.id);

let response = reqwest::Client::new()
.get(url)
Expand Down
Loading
Loading