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).
156 lines
5.9 KiB
Rust
156 lines
5.9 KiB
Rust
use mongodb::{bson::doc, options::IndexOptions, Collection, IndexModel};
|
|
|
|
use anyhow::Result;
|
|
|
|
/// Creates required collections and indexes. Best-effort by design: index
|
|
/// creation failures are logged as warnings and do not abort startup, matching
|
|
/// the existing swallow-on-warning style. Safe to run repeatedly (idempotent).
|
|
pub struct DatabaseInitializer {
|
|
db: mongodb::Database,
|
|
}
|
|
|
|
impl DatabaseInitializer {
|
|
pub fn new(db: mongodb::Database) -> Self {
|
|
Self { db }
|
|
}
|
|
|
|
pub async fn initialize(&self) -> Result<()> {
|
|
println!("[MongoDB] Initializing database collections and indexes...");
|
|
|
|
// Create users collection and index
|
|
{
|
|
let collection: Collection<mongodb::bson::Document> = self.db.collection("users");
|
|
|
|
// Create email index using the builder pattern
|
|
let index = IndexModel::builder()
|
|
.keys(doc! { "email": 1 })
|
|
.options(IndexOptions::builder().unique(true).build())
|
|
.build();
|
|
|
|
match collection.create_index(index, None).await {
|
|
Ok(_) => println!("✓ Created index on users.email"),
|
|
Err(e) => println!("Warning: Failed to create index on users.email: {}", e),
|
|
}
|
|
}
|
|
|
|
// Create families collection and indexes
|
|
{
|
|
let collection: Collection<mongodb::bson::Document> = self.db.collection("families");
|
|
|
|
let index1 = IndexModel::builder().keys(doc! { "userId": 1 }).build();
|
|
|
|
let index2 = IndexModel::builder().keys(doc! { "familyId": 1 }).build();
|
|
|
|
match collection.create_index(index1, None).await {
|
|
Ok(_) => println!("✓ Created index on families.userId"),
|
|
Err(e) => println!("Warning: Failed to create index on families.userId: {}", e),
|
|
}
|
|
|
|
match collection.create_index(index2, None).await {
|
|
Ok(_) => println!("✓ Created index on families.familyId"),
|
|
Err(e) => println!(
|
|
"Warning: Failed to create index on families.familyId: {}",
|
|
e
|
|
),
|
|
}
|
|
}
|
|
|
|
// Create profiles collection and index
|
|
{
|
|
let collection: Collection<mongodb::bson::Document> = self.db.collection("profiles");
|
|
|
|
let index = IndexModel::builder().keys(doc! { "familyId": 1 }).build();
|
|
|
|
match collection.create_index(index, None).await {
|
|
Ok(_) => println!("✓ Created index on profiles.familyId"),
|
|
Err(e) => println!(
|
|
"Warning: Failed to create index on profiles.familyId: {}",
|
|
e
|
|
),
|
|
}
|
|
}
|
|
|
|
// Create health_data collection
|
|
{
|
|
let _collection: Collection<mongodb::bson::Document> =
|
|
self.db.collection("health_data");
|
|
println!("✓ Created health_data collection");
|
|
}
|
|
|
|
// Create lab_results collection
|
|
{
|
|
let _collection: Collection<mongodb::bson::Document> =
|
|
self.db.collection("lab_results");
|
|
println!("✓ Created lab_results collection");
|
|
}
|
|
|
|
// Create medications collection
|
|
{
|
|
let _collection: Collection<mongodb::bson::Document> =
|
|
self.db.collection("medications");
|
|
println!("✓ Created medications collection");
|
|
}
|
|
|
|
// Create appointments collection
|
|
{
|
|
let _collection: Collection<mongodb::bson::Document> =
|
|
self.db.collection("appointments");
|
|
println!("✓ Created appointments collection");
|
|
}
|
|
|
|
// Create shares collection and index
|
|
{
|
|
let collection: Collection<mongodb::bson::Document> = self.db.collection("shares");
|
|
|
|
let index = IndexModel::builder().keys(doc! { "familyId": 1 }).build();
|
|
|
|
match collection.create_index(index, None).await {
|
|
Ok(_) => println!("✓ Created index on shares.familyId"),
|
|
Err(e) => println!("Warning: Failed to create index on shares.familyId: {}", e),
|
|
}
|
|
}
|
|
|
|
// Create refresh_tokens collection and indexes.
|
|
// - unique index on tokenHash (the only thing we ever look tokens up by)
|
|
// - TTL index on expiresAt so expired tokens are auto-deleted by Mongo
|
|
{
|
|
let collection: Collection<mongodb::bson::Document> =
|
|
self.db.collection("refresh_tokens");
|
|
|
|
let unique_index = IndexModel::builder()
|
|
.keys(doc! { "tokenHash": 1 })
|
|
.options(IndexOptions::builder().unique(true).build())
|
|
.build();
|
|
|
|
// TTL: documents are deleted once their `expiresAt` timestamp passes.
|
|
// With expireAfterSeconds=0, Mongo removes each doc exactly when the
|
|
// datetime stored in `expiresAt` is reached.
|
|
let ttl_index = IndexModel::builder()
|
|
.keys(doc! { "expiresAt": 1 })
|
|
.options(
|
|
IndexOptions::builder()
|
|
.expire_after(std::time::Duration::from_secs(0))
|
|
.build(),
|
|
)
|
|
.build();
|
|
|
|
match collection.create_index(unique_index, None).await {
|
|
Ok(_) => println!("✓ Created unique index on refresh_tokens.tokenHash"),
|
|
Err(e) => println!(
|
|
"Warning: Failed to create index on refresh_tokens.tokenHash: {}",
|
|
e
|
|
),
|
|
}
|
|
match collection.create_index(ttl_index, None).await {
|
|
Ok(_) => println!("✓ Created TTL index on refresh_tokens.expiresAt"),
|
|
Err(e) => println!(
|
|
"Warning: Failed to create TTL index on refresh_tokens.expiresAt: {}",
|
|
e
|
|
),
|
|
}
|
|
}
|
|
|
|
println!("[MongoDB] Database initialization complete");
|
|
Ok(())
|
|
}
|
|
}
|