fix(backend): medication get/update/delete use medication_id (UUID), not Mongo _id

The handlers parsed the URL :id as a Mongo ObjectId, but the create handler
generates medication_id as a UUID — so update/delete/get-by-uuid returned 400.
Added find_by_medication_id/update_by_medication_id/delete_by_medication_id
(filter on the medicationId field) and switched the get/update/delete handlers
to use them. Update now actually persists.
This commit is contained in:
goose 2026-06-28 11:40:03 -03:00
parent d41256e05b
commit dadcc8f1e4
2 changed files with 128 additions and 22 deletions

View file

@ -2,7 +2,6 @@ use axum::{
extract::{Extension, Json, Path, Query, State}, extract::{Extension, Json, Path, Query, State},
http::StatusCode, http::StatusCode,
}; };
use mongodb::bson::oid::ObjectId;
use std::time::SystemTime; use std::time::SystemTime;
use crate::{ use crate::{
@ -109,13 +108,12 @@ pub async fn get_medication(
let database = state.db.get_database(); let database = state.db.get_database();
let repo = MedicationRepository::new(database.collection("medications")); let repo = MedicationRepository::new(database.collection("medications"));
match ObjectId::parse_str(&id) { // The path param is the application-level medication_id (a UUID), not the
Ok(oid) => match repo.find_by_id(&oid).await { // Mongo _id, so look it up directly instead of parsing as ObjectId.
match repo.find_by_medication_id(&id).await {
Ok(Some(medication)) => Ok(Json(medication.into())), Ok(Some(medication)) => Ok(Json(medication.into())),
Ok(None) => Err(StatusCode::NOT_FOUND), Ok(None) => Err(StatusCode::NOT_FOUND),
Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR), Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
},
Err(_) => Err(StatusCode::BAD_REQUEST),
} }
} }
@ -128,13 +126,11 @@ pub async fn update_medication(
let database = state.db.get_database(); let database = state.db.get_database();
let repo = MedicationRepository::new(database.collection("medications")); let repo = MedicationRepository::new(database.collection("medications"));
match ObjectId::parse_str(&id) { // Look up by medication_id (UUID) — the path param is not a Mongo ObjectId.
Ok(oid) => match repo.update(&oid, req).await { match repo.update_by_medication_id(&id, req).await {
Ok(Some(medication)) => Ok(Json(medication.into())), Ok(Some(medication)) => Ok(Json(medication.into())),
Ok(None) => Err(StatusCode::NOT_FOUND), Ok(None) => Err(StatusCode::NOT_FOUND),
Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR), Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
},
Err(_) => Err(StatusCode::BAD_REQUEST),
} }
} }
@ -146,13 +142,11 @@ pub async fn delete_medication(
let database = state.db.get_database(); let database = state.db.get_database();
let repo = MedicationRepository::new(database.collection("medications")); let repo = MedicationRepository::new(database.collection("medications"));
match ObjectId::parse_str(&id) { // Look up by medication_id (UUID), not Mongo _id.
Ok(oid) => match repo.delete(&oid).await { match repo.delete_by_medication_id(&id).await {
Ok(true) => Ok(StatusCode::NO_CONTENT), Ok(true) => Ok(StatusCode::NO_CONTENT),
Ok(false) => Err(StatusCode::NOT_FOUND), Ok(false) => Err(StatusCode::NOT_FOUND),
Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR), Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
},
Err(_) => Err(StatusCode::BAD_REQUEST),
} }
} }

View file

@ -362,6 +362,118 @@ impl MedicationRepository {
Ok(medication) Ok(medication)
} }
/// Look up by the application-level medication_id (a UUID string), which is
/// what the API exposes in URLs. (find_by_id filters on the Mongo _id.)
pub async fn find_by_medication_id(
&self,
medication_id: &str,
) -> Result<Option<Medication>, Box<dyn std::error::Error>> {
let filter = doc! { "medicationId": medication_id };
let medication = self.collection.find_one(filter, None).await?;
Ok(medication)
}
/// Update by the application-level medication_id (UUID). The API exposes
/// medication_id in URLs, not the Mongo _id.
pub async fn update_by_medication_id(
&self,
medication_id: &str,
updates: UpdateMedicationRequest,
) -> Result<Option<Medication>, Box<dyn std::error::Error>> {
let filter = doc! { "medicationId": medication_id };
let existing = self.collection.find_one(filter.clone(), None).await?;
let Some(existing) = existing else {
return Ok(None);
};
let mut data: MedicationData =
serde_json::from_str(&existing.medication_data.data).unwrap_or_default();
if let Some(v) = updates.name {
data.name = v;
}
if let Some(v) = updates.dosage {
data.dosage = v;
}
if let Some(v) = updates.frequency {
data.frequency = v;
}
if let Some(v) = updates.route {
data.route = v;
}
if let Some(v) = updates.reason {
data.reason = Some(v);
}
if let Some(v) = updates.instructions {
data.instructions = Some(v);
}
if let Some(v) = updates.side_effects {
data.side_effects = v;
}
if let Some(v) = updates.prescribed_by {
data.prescribed_by = Some(v);
}
if let Some(v) = updates.prescribed_date {
data.prescribed_date = Some(v);
}
if let Some(v) = updates.start_date {
data.start_date = Some(v);
}
if let Some(v) = updates.end_date {
data.end_date = Some(v);
}
if let Some(v) = updates.notes {
data.notes = Some(v);
}
if let Some(v) = updates.tags {
data.tags = v;
}
// NOTE: updates.active is accepted by the API but not yet persisted.
let data_json = serde_json::json!({
"name": data.name,
"dosage": data.dosage,
"frequency": data.frequency,
"route": data.route,
"reason": data.reason,
"instructions": data.instructions,
"sideEffects": data.side_effects,
"prescribedBy": data.prescribed_by,
"prescribedDate": data.prescribed_date,
"startDate": data.start_date,
"endDate": data.end_date,
"notes": data.notes,
"tags": data.tags,
})
.to_string();
let mut update_doc = doc! {
"medicationData.data": data_json,
"updatedAt": DateTime::now(),
};
if let Some(pill_identification) = updates.pill_identification {
if let Ok(pill_doc) = mongodb::bson::to_document(&pill_identification) {
update_doc.insert("pill_identification", pill_doc);
}
}
let medication = self
.collection
.find_one_and_update(filter, doc! { "$set": update_doc }, None)
.await?;
Ok(medication)
}
/// Delete by the application-level medication_id (UUID).
pub async fn delete_by_medication_id(
&self,
medication_id: &str,
) -> Result<bool, Box<dyn std::error::Error>> {
let filter = doc! { "medicationId": medication_id };
let result = self.collection.delete_one(filter, None).await?;
Ok(result.deleted_count > 0)
}
pub async fn update( pub async fn update(
&self, &self,
id: &ObjectId, id: &ObjectId,