feat(backend): migration mekanizması, indeksler ve ortak model yardımcıları

- db/migrations.rs: schema_migrations ile sürümlü migration (v1: koleksiyonlar)
- db/indexes.rs: DATA_MODEL indeksleri + senkronizasyon indeksi, açılışta otomatik
- models/common.rs: UUID v7, ortak alanlar, hane kapsamlı filtre
- Birim testleri (4)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-11 23:25:55 +03:00
parent 8ec24ecec2
commit 5550c7c43d
13 changed files with 367 additions and 34 deletions
+83
View File
@@ -0,0 +1,83 @@
//! Basit şema migration mekanizması (DATA_MODEL.md §3.15).
//!
//! Her migration bir kez çalışır; `schema_migrations` koleksiyonuna
//! `{ _id: sürüm, name, applied_at }` olarak kaydedilir.
//! Yeni migration eklemek: `MIGRATIONS` listesine bir sonraki sürüm numarasıyla ekle.
use std::{future::Future, pin::Pin};
use mongodb::{
Database,
bson::{DateTime, Document, doc},
error::Result,
};
use super::col;
type MigrationFn =
for<'a> fn(&'a Database) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'a>>;
struct Migration {
version: i32,
name: &'static str,
run: MigrationFn,
}
/// Sıralı migration listesi. Sürümler 1'den başlar ve atlamadan artar.
const MIGRATIONS: &[Migration] = &[Migration {
version: 1,
name: "initial",
run: |db| Box::pin(m001_initial(db)),
}];
/// Uygulanmamış migration'ları sırayla çalıştırır.
pub async fn run(db: &Database) -> Result<()> {
let coll = db.collection::<Document>(col::SCHEMA_MIGRATIONS);
for m in MIGRATIONS {
if coll.find_one(doc! { "_id": m.version }).await?.is_some() {
continue;
}
tracing::info!("Migration çalışıyor: v{} ({})", m.version, m.name);
(m.run)(db).await?;
coll.insert_one(doc! {
"_id": m.version,
"name": m.name,
"applied_at": DateTime::now(),
})
.await?;
}
Ok(())
}
/// v1: Tüm koleksiyonları açıkça oluşturur.
/// (Transaction içinde ilk kez yazılan koleksiyonlar sorun çıkarmasın diye önceden var olsunlar.)
async fn m001_initial(db: &Database) -> Result<()> {
let existing = db.list_collection_names().await?;
let all = col::HOUSEHOLD_SCOPED
.iter()
.chain(&[col::EXCHANGE_RATES, col::SCHEMA_MIGRATIONS]);
for name in all {
if !existing.iter().any(|e| e == name) {
db.create_collection(*name).await?;
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn versions_start_at_1_and_are_sequential() {
for (i, m) in MIGRATIONS.iter().enumerate() {
assert_eq!(
m.version,
i as i32 + 1,
"migration sürümleri ardışık olmalı"
);
}
}
}