Refresh-token rotation bug (found via Solaria smoke test): * RefreshClaims now carries a unique random jti (UUID v4). Without it, two refresh tokens issued in the same second (e.g. on rotation) were byte-identical — breaking rotation, colliding on the tokenHash unique index, and making a stolen old token indistinguishable from the new one. Every refresh token is now unique regardless of issue time. Added a unit test asserting consecutive tokens differ. Code cleanup (review items): * #15: removed unused deps tower_governor, slog, thiserror (zero refs in src/). * #30: deleted leftover src/main.rs.restore (a stale git-error-message backup). * #28: removed the 9 single-line-comment stub files under src/db/ (appointment, family, health_data, lab_result, medication, profile, permission, share, user) and their mod declarations; nothing referenced them, real logic lives in mongodb_impl.rs/init.rs. * #29: stripped 61 broad module-level #![allow(...)] suppressions across src/. Fixed the surfaced warnings instead: 4 unused 'claims' extractor bindings -> _claims; removed dead OpenFDAService.client/base_url fields + the never-called query_drug_events method + unused HashMap/reqwest imports; applied clippy's mechanical fixes (Ok(?) -> ?, Copy ObjectId clone, redundant closures, useless conversions); rewrote 'if let Ok(_) = x' -> 'if x.is_ok()'. Result: cargo build + clippy --all-targets are warning-free with no blanket allows. Verified: fmt clean, build clean, clippy 0 warnings, 19 unit tests pass (was 18; +1 jti-uniqueness test).
97 lines
2.7 KiB
Rust
97 lines
2.7 KiB
Rust
//! Drug Interaction Handlers (Phase 2.8)
|
|
|
|
use axum::{
|
|
extract::{Extension, State},
|
|
http::StatusCode,
|
|
Json,
|
|
};
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use crate::{
|
|
auth::jwt::Claims,
|
|
config::AppState,
|
|
services::openfda_service::{DrugInteraction, InteractionSeverity},
|
|
};
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
pub struct CheckInteractionRequest {
|
|
pub medications: Vec<String>,
|
|
}
|
|
|
|
#[derive(Debug, Serialize)]
|
|
pub struct InteractionResponse {
|
|
pub interactions: Vec<DrugInteraction>,
|
|
pub has_severe: bool,
|
|
pub disclaimer: String,
|
|
}
|
|
|
|
/// Check interactions between medications
|
|
pub async fn check_interactions(
|
|
_claims: Extension<Claims>,
|
|
State(state): State<AppState>,
|
|
Json(request): Json<CheckInteractionRequest>,
|
|
) -> Result<Json<InteractionResponse>, StatusCode> {
|
|
if request.medications.len() < 2 {
|
|
return Err(StatusCode::BAD_REQUEST);
|
|
}
|
|
|
|
let interaction_service = state
|
|
.interaction_service
|
|
.as_ref()
|
|
.ok_or(StatusCode::SERVICE_UNAVAILABLE)?;
|
|
|
|
match interaction_service
|
|
.check_eu_medications(&request.medications)
|
|
.await
|
|
{
|
|
Ok(interactions) => {
|
|
let has_severe = interactions
|
|
.iter()
|
|
.any(|i| matches!(i.severity, InteractionSeverity::Severe));
|
|
|
|
Ok(Json(InteractionResponse {
|
|
interactions,
|
|
has_severe,
|
|
disclaimer: "This information is advisory only. Consult with a physician for detailed information about drug interactions.".to_string(),
|
|
}))
|
|
}
|
|
Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
pub struct CheckNewMedicationRequest {
|
|
pub new_medication: String,
|
|
pub existing_medications: Vec<String>,
|
|
}
|
|
|
|
#[derive(Debug, Serialize)]
|
|
pub struct NewMedicationCheckResult {
|
|
pub interactions: Vec<DrugInteraction>,
|
|
pub has_severe: bool,
|
|
pub disclaimer: String,
|
|
}
|
|
|
|
/// Check if a new medication has interactions with existing medications
|
|
pub async fn check_new_medication(
|
|
_claims: Extension<Claims>,
|
|
State(state): State<AppState>,
|
|
Json(request): Json<CheckNewMedicationRequest>,
|
|
) -> Result<Json<InteractionResponse>, StatusCode> {
|
|
let interaction_service = state
|
|
.interaction_service
|
|
.as_ref()
|
|
.ok_or(StatusCode::SERVICE_UNAVAILABLE)?;
|
|
|
|
match interaction_service
|
|
.check_new_medication(&request.new_medication, &request.existing_medications)
|
|
.await
|
|
{
|
|
Ok(result) => Ok(Json(InteractionResponse {
|
|
interactions: result.interactions,
|
|
has_severe: result.has_severe,
|
|
disclaimer: result.disclaimer,
|
|
})),
|
|
Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
|
|
}
|
|
}
|