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:
Generated
+1
@@ -959,6 +959,7 @@ dependencies = [
|
|||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
@@ -13,3 +13,4 @@ thiserror = "2.0.21"
|
|||||||
tokio = { version = "1.54.1", features = ["full"] }
|
tokio = { version = "1.54.1", features = ["full"] }
|
||||||
tracing = "0.1.44"
|
tracing = "0.1.44"
|
||||||
tracing-subscriber = { version = "0.3.23", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3.23", features = ["env-filter"] }
|
||||||
|
uuid = { version = "1.29.2", features = ["v7"] }
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
//! MongoDB bağlantısı.
|
|
||||||
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use mongodb::{Client, Database, bson::doc, options::ClientOptions};
|
|
||||||
|
|
||||||
/// URI'de veritabanı adı yoksa kullanılacak ad.
|
|
||||||
const DEFAULT_DB_NAME: &str = "kakeibo";
|
|
||||||
|
|
||||||
/// Bağlantıyı kurar ve bir `ping` ile sunucunun gerçekten erişilebilir olduğunu doğrular.
|
|
||||||
pub async fn connect(uri: &str) -> mongodb::error::Result<Database> {
|
|
||||||
let mut options = ClientOptions::parse(uri).await?;
|
|
||||||
options.app_name = Some("kakeibo".into());
|
|
||||||
// Sunucu yoksa 30 sn yerine 5 sn içinde hata ver.
|
|
||||||
options.server_selection_timeout = Some(Duration::from_secs(5));
|
|
||||||
|
|
||||||
let db_name = options
|
|
||||||
.default_database
|
|
||||||
.clone()
|
|
||||||
.unwrap_or_else(|| DEFAULT_DB_NAME.into());
|
|
||||||
|
|
||||||
let client = Client::with_options(options)?;
|
|
||||||
let db = client.database(&db_name);
|
|
||||||
ping(&db).await?;
|
|
||||||
Ok(db)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn ping(db: &Database) -> mongodb::error::Result<()> {
|
|
||||||
db.run_command(doc! { "ping": 1 }).await.map(|_| ())
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
//! İndeksler (DATA_MODEL.md §4). Açılışta her seferinde çağrılır;
|
||||||
|
//! MongoDB zaten var olan aynı indeksi yeniden oluşturmaz.
|
||||||
|
|
||||||
|
use mongodb::{
|
||||||
|
Database, IndexModel,
|
||||||
|
bson::{Document, doc},
|
||||||
|
error::Result,
|
||||||
|
options::IndexOptions,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::col;
|
||||||
|
|
||||||
|
pub async fn ensure(db: &Database) -> Result<()> {
|
||||||
|
for (collection, indexes) in definitions() {
|
||||||
|
db.collection::<Document>(collection)
|
||||||
|
.create_indexes(indexes)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
tracing::info!("İndeksler hazır");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn idx(keys: Document) -> IndexModel {
|
||||||
|
IndexModel::builder().keys(keys).build()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn idx_opts(keys: Document, options: IndexOptions) -> IndexModel {
|
||||||
|
IndexModel::builder().keys(keys).options(options).build()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unique() -> IndexOptions {
|
||||||
|
IndexOptions::builder().unique(true).build()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn definitions() -> Vec<(&'static str, Vec<IndexModel>)> {
|
||||||
|
let hp = || idx(doc! { "household_id": 1, "period_id": 1 });
|
||||||
|
|
||||||
|
let mut defs = vec![
|
||||||
|
(col::USERS, vec![idx_opts(doc! { "email": 1 }, unique())]),
|
||||||
|
(col::INVITES, vec![idx_opts(doc! { "code": 1 }, unique())]),
|
||||||
|
(
|
||||||
|
col::PERIODS,
|
||||||
|
vec![
|
||||||
|
idx(doc! { "household_id": 1, "start_date": 1 }),
|
||||||
|
// Hanede en fazla bir açık dönem
|
||||||
|
idx_opts(
|
||||||
|
doc! { "household_id": 1 },
|
||||||
|
IndexOptions::builder()
|
||||||
|
.name("one_open_period_per_household".to_string())
|
||||||
|
.unique(true)
|
||||||
|
.partial_filter_expression(doc! { "status": "open" })
|
||||||
|
.build(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
col::CATEGORIES,
|
||||||
|
vec![idx(doc! { "household_id": 1, "sort_order": 1 })],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
col::PAYMENT_METHODS,
|
||||||
|
vec![idx(doc! { "household_id": 1, "sort_order": 1 })],
|
||||||
|
),
|
||||||
|
(col::PLAN_ITEMS, vec![hp()]),
|
||||||
|
(col::BUDGETS, vec![hp()]),
|
||||||
|
(col::NOTES, vec![hp()]),
|
||||||
|
(
|
||||||
|
col::ENTRIES,
|
||||||
|
vec![
|
||||||
|
idx(doc! { "household_id": 1, "period_id": 1, "date": 1 }),
|
||||||
|
idx(doc! { "household_id": 1, "date": 1 }),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
col::CARRYOVERS,
|
||||||
|
vec![idx(doc! { "household_id": 1, "to_period_id": 1 })],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
col::ASSET_TRANSACTIONS,
|
||||||
|
vec![idx(doc! { "household_id": 1, "asset_id": 1, "date": 1 })],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
col::EXCHANGE_RATES,
|
||||||
|
vec![idx(doc! { "code": 1, "household_id": 1, "rate_date": -1 })],
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Senkronizasyon (Faz 11): "şu tarihten sonra değişenler" sorgusu.
|
||||||
|
for &name in col::HOUSEHOLD_SCOPED {
|
||||||
|
let sync = idx(doc! { "household_id": 1, "updated_at": 1 });
|
||||||
|
match defs.iter_mut().find(|(c, _)| *c == name) {
|
||||||
|
Some((_, list)) => list.push(sync),
|
||||||
|
None => defs.push((name, vec![sync])),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
defs
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn every_household_collection_has_sync_index() {
|
||||||
|
let defs = definitions();
|
||||||
|
for &name in col::HOUSEHOLD_SCOPED {
|
||||||
|
let (_, list) = defs.iter().find(|(c, _)| *c == name).unwrap();
|
||||||
|
assert!(
|
||||||
|
list.iter()
|
||||||
|
.any(|i| i.keys == doc! { "household_id": 1, "updated_at": 1 }),
|
||||||
|
"{name} koleksiyonunda senkronizasyon indeksi yok"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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ı"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
//! MongoDB bağlantısı, koleksiyon adları, migration ve indeksler.
|
||||||
|
|
||||||
|
pub mod indexes;
|
||||||
|
pub mod migrations;
|
||||||
|
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use mongodb::{Client, Database, bson::doc, options::ClientOptions};
|
||||||
|
|
||||||
|
/// URI'de veritabanı adı yoksa kullanılacak ad.
|
||||||
|
const DEFAULT_DB_NAME: &str = "kakeibo";
|
||||||
|
|
||||||
|
/// Bağlantıyı kurar ve bir `ping` ile sunucunun gerçekten erişilebilir olduğunu doğrular.
|
||||||
|
pub async fn connect(uri: &str) -> mongodb::error::Result<Database> {
|
||||||
|
let mut options = ClientOptions::parse(uri).await?;
|
||||||
|
options.app_name = Some("kakeibo".into());
|
||||||
|
// Sunucu yoksa 30 sn yerine 5 sn içinde hata ver.
|
||||||
|
options.server_selection_timeout = Some(Duration::from_secs(5));
|
||||||
|
|
||||||
|
let db_name = options
|
||||||
|
.default_database
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_else(|| DEFAULT_DB_NAME.into());
|
||||||
|
|
||||||
|
let client = Client::with_options(options)?;
|
||||||
|
let db = client.database(&db_name);
|
||||||
|
ping(&db).await?;
|
||||||
|
Ok(db)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn ping(db: &Database) -> mongodb::error::Result<()> {
|
||||||
|
db.run_command(doc! { "ping": 1 }).await.map(|_| ())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Açılışta çalışır: bekleyen migration'lar, ardından indeksler.
|
||||||
|
/// İkisi de tekrar çalıştırılabilir (idempotent); zaten yapılmış olanı atlar.
|
||||||
|
pub async fn prepare(db: &Database) -> mongodb::error::Result<()> {
|
||||||
|
migrations::run(db).await?;
|
||||||
|
indexes::ensure(db).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Koleksiyon adları (DATA_MODEL.md §3). Yazım hatasını derleyici yakalasın diye sabit.
|
||||||
|
#[allow(dead_code)] // Bazıları sonraki fazlarda kullanılacak
|
||||||
|
pub mod col {
|
||||||
|
pub const USERS: &str = "users";
|
||||||
|
pub const HOUSEHOLDS: &str = "households";
|
||||||
|
pub const INVITES: &str = "invites";
|
||||||
|
pub const PERIODS: &str = "periods";
|
||||||
|
pub const CATEGORIES: &str = "categories";
|
||||||
|
pub const PAYMENT_METHODS: &str = "payment_methods";
|
||||||
|
pub const PLAN_ITEMS: &str = "plan_items";
|
||||||
|
pub const BUDGETS: &str = "budgets";
|
||||||
|
pub const ENTRIES: &str = "entries";
|
||||||
|
pub const CARRYOVERS: &str = "carryovers";
|
||||||
|
pub const NOTES: &str = "notes";
|
||||||
|
pub const ASSETS: &str = "assets";
|
||||||
|
pub const ASSET_TRANSACTIONS: &str = "asset_transactions";
|
||||||
|
pub const EXCHANGE_RATES: &str = "exchange_rates";
|
||||||
|
pub const SCHEMA_MIGRATIONS: &str = "schema_migrations";
|
||||||
|
|
||||||
|
/// Hane verisi taşıyan koleksiyonlar (`household_id` alanı var).
|
||||||
|
pub const HOUSEHOLD_SCOPED: &[&str] = &[
|
||||||
|
USERS,
|
||||||
|
HOUSEHOLDS,
|
||||||
|
INVITES,
|
||||||
|
PERIODS,
|
||||||
|
CATEGORIES,
|
||||||
|
PAYMENT_METHODS,
|
||||||
|
PLAN_ITEMS,
|
||||||
|
BUDGETS,
|
||||||
|
ENTRIES,
|
||||||
|
CARRYOVERS,
|
||||||
|
NOTES,
|
||||||
|
ASSETS,
|
||||||
|
ASSET_TRANSACTIONS,
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ mod config;
|
|||||||
mod db;
|
mod db;
|
||||||
#[allow(dead_code)] // AppError, Faz 1 – Adım 3 itibarıyla route'larda kullanılacak
|
#[allow(dead_code)] // AppError, Faz 1 – Adım 3 itibarıyla route'larda kullanılacak
|
||||||
mod error;
|
mod error;
|
||||||
|
mod models;
|
||||||
mod routes;
|
mod routes;
|
||||||
mod state;
|
mod state;
|
||||||
|
|
||||||
@@ -44,6 +45,11 @@ async fn main() -> ExitCode {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if let Err(e) = db::prepare(&db).await {
|
||||||
|
tracing::error!("Veritabanı hazırlanamadı (migration/indeks): {e}");
|
||||||
|
return ExitCode::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
let port = config.port;
|
let port = config.port;
|
||||||
let state = AppState {
|
let state = AppState {
|
||||||
db,
|
db,
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
//! Tüm koleksiyonlarda ortak alanlar için yardımcılar (DATA_MODEL.md §1.1).
|
||||||
|
//!
|
||||||
|
//! Her kayıt şu alanları taşır:
|
||||||
|
//! `_id` (UUID v7), `household_id`, `created_at`, `updated_at`, `deleted_at`, `version`
|
||||||
|
|
||||||
|
#![allow(dead_code)] // Faz 1 – Adım 3 itibarıyla kullanılacak
|
||||||
|
|
||||||
|
use mongodb::bson::{DateTime, Document, Uuid, doc};
|
||||||
|
|
||||||
|
/// Yeni UUID v7 üretir. İlk 48 bit zaman damgası olduğu için
|
||||||
|
/// sonradan üretilen kimlik her zaman daha büyüktür (zamana göre sıralanabilir).
|
||||||
|
pub fn new_id() -> Uuid {
|
||||||
|
Uuid::from_bytes(uuid::Uuid::now_v7().into_bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Şu anki zaman (UTC, BSON DateTime).
|
||||||
|
pub fn now() -> DateTime {
|
||||||
|
DateTime::now()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Yeni kayıt için ortak alanlar. Modele özgü alanlarla birleştirilir:
|
||||||
|
/// `let mut d = new_meta(hid); d.insert("name", "Market");`
|
||||||
|
pub fn new_meta(household_id: Uuid) -> Document {
|
||||||
|
let ts = now();
|
||||||
|
doc! {
|
||||||
|
"_id": new_id(),
|
||||||
|
"household_id": household_id,
|
||||||
|
"created_at": ts,
|
||||||
|
"updated_at": ts,
|
||||||
|
"deleted_at": null,
|
||||||
|
"version": 1_i32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Hane kapsamlı, silinmemiş kayıtlar için temel filtre.
|
||||||
|
/// Tüm okuma sorguları bununla başlar → başka hanenin verisi asla dönmez.
|
||||||
|
pub fn scope(household_id: Uuid) -> Document {
|
||||||
|
doc! { "household_id": household_id, "deleted_at": null }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Güncellemelerde eklenecek ortak kısım: `updated_at` yenilenir, `version` +1.
|
||||||
|
pub fn touch() -> Document {
|
||||||
|
doc! {
|
||||||
|
"$set": { "updated_at": now() },
|
||||||
|
"$inc": { "version": 1_i32 },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn new_id_is_v7_and_sortable() {
|
||||||
|
let a = uuid::Uuid::from_bytes(new_id().bytes());
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(2));
|
||||||
|
let b = uuid::Uuid::from_bytes(new_id().bytes());
|
||||||
|
|
||||||
|
assert_eq!(a.get_version_num(), 7);
|
||||||
|
assert!(b > a, "sonra üretilen UUID daha büyük olmalı");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn scope_always_filters_household_and_deleted() {
|
||||||
|
let hid = new_id();
|
||||||
|
let f = scope(hid);
|
||||||
|
assert_eq!(f.get("household_id"), Some(&hid.into()));
|
||||||
|
assert!(f.contains_key("deleted_at"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
//! Veritabanı modelleri. Her koleksiyonun yapısı, onu kullanan adımda eklenir.
|
||||||
|
//! Şema: `docs/DATA_MODEL.md`
|
||||||
|
|
||||||
|
pub mod common;
|
||||||
+2
-1
@@ -60,7 +60,7 @@ Renkler ve ikonlar frontend tasarım token'larında.
|
|||||||
|
|
||||||
### 3.2 `households`
|
### 3.2 `households`
|
||||||
```js
|
```js
|
||||||
{ _id, name, owner_id,
|
{ _id, household_id /* = _id */, name, owner_id,
|
||||||
settings: {
|
settings: {
|
||||||
currency: "TRY",
|
currency: "TRY",
|
||||||
budget_split_pct: { needs: 50, wants: 30, culture: 10, unexpected: 10 },
|
budget_split_pct: { needs: 50, wants: 30, culture: 10, unexpected: 10 },
|
||||||
@@ -68,6 +68,7 @@ Renkler ve ikonlar frontend tasarım token'larında.
|
|||||||
},
|
},
|
||||||
created_at, updated_at, deleted_at, version }
|
created_at, updated_at, deleted_at, version }
|
||||||
```
|
```
|
||||||
|
- `household_id` = `_id` (ortak `scope()` filtresi tüm koleksiyonlarda aynı çalışsın diye).
|
||||||
- Prompttaki `settings` koleksiyonu yerine **haneye gömülü** (her hanede tek kayıt olduğu için ayrı koleksiyona gerek yok).
|
- Prompttaki `settings` koleksiyonu yerine **haneye gömülü** (her hanede tek kayıt olduğu için ayrı koleksiyona gerek yok).
|
||||||
|
|
||||||
### 3.3 `invites`
|
### 3.3 `invites`
|
||||||
|
|||||||
+4
-1
@@ -1,7 +1,7 @@
|
|||||||
# 🧠 Proje Hafızası
|
# 🧠 Proje Hafızası
|
||||||
|
|
||||||
## ▶️ Sıradaki Adım
|
## ▶️ Sıradaki Adım
|
||||||
**Faz 1 – Adım 2:** Ortak model alanları (UUID v7, zaman damgaları, version), `schema_migrations` ile migration mekanizması, `DATA_MODEL.md` §4 indeksleri (açılışta otomatik).
|
**Faz 1 – Adım 3:** Kimlik doğrulama: `POST /api/auth/register` (kullanıcı + hane + örnek kategori/ödeme türü, transaction), `login`, `logout`, `GET /api/auth/me`; argon2, JWT httpOnly cookie, giriş rate limit.
|
||||||
|
|
||||||
## 📌 Alınan Kararlar
|
## 📌 Alınan Kararlar
|
||||||
- Sürüm kontrolü: Gitea (`https://gitea.ta1sph.info/suphicakir/kakeibo.git`), dal `main`.
|
- Sürüm kontrolü: Gitea (`https://gitea.ta1sph.info/suphicakir/kakeibo.git`), dal `main`.
|
||||||
@@ -11,6 +11,8 @@
|
|||||||
- Yerel MongoDB host portu 27020 (27017 sistem mongod, 27018/27019 başka projeler).
|
- Yerel MongoDB host portu 27020 (27017 sistem mongod, 27018/27019 başka projeler).
|
||||||
- Frontend lint aracı: oxlint (Vite şablonu varsayılanı), ESLint yerine.
|
- Frontend lint aracı: oxlint (Vite şablonu varsayılanı), ESLint yerine.
|
||||||
- Veri modeli v1 onaylandı (2026-11-11): ölçekli tam sayı (`_e6`), tarih `YYYY-MM-DD` metin, dönem bitişi hariç, kullanıcı = tek hane, ayarlar haneye gömülü, haftalık bütçe hesaplanır.
|
- Veri modeli v1 onaylandı (2026-11-11): ölçekli tam sayı (`_e6`), tarih `YYYY-MM-DD` metin, dönem bitişi hariç, kullanıcı = tek hane, ayarlar haneye gömülü, haftalık bütçe hesaplanır.
|
||||||
|
- Model yaklaşımı: `#[serde(flatten)]` yerine ortak alanlar `models/common.rs` yardımcılarıyla (`new_meta`, `scope`, `touch`). Modeller ilgili adımda eklenir.
|
||||||
|
- `households.household_id = _id` (scope filtresi her yerde aynı).
|
||||||
- Sürümler: Rust 1.99.1 (edition 2024), Axum 0.8, React 19, Vite 8, TypeScript 7.
|
- Sürümler: Rust 1.99.1 (edition 2024), Axum 0.8, React 19, Vite 8, TypeScript 7.
|
||||||
|
|
||||||
## ❓ Açık Konular
|
## ❓ Açık Konular
|
||||||
@@ -30,3 +32,4 @@
|
|||||||
|
|
||||||
### 2026-11-11 — Faz 1
|
### 2026-11-11 — Faz 1
|
||||||
- **Adım 1:** `config.rs` (env + doğrulama, JWT_SECRET ≥ 32), `db.rs` (5 sn timeout + ping), `error.rs` (`AppError` → `{error:{code,message}}` Türkçe; 5xx detayı yalnızca logda), `state.rs`, `routes/` (health: DB yoksa 503). Açılışta DB/ayar hatasında anlaşılır log + çıkış kodu 1. Elle test: 4 senaryo geçti.
|
- **Adım 1:** `config.rs` (env + doğrulama, JWT_SECRET ≥ 32), `db.rs` (5 sn timeout + ping), `error.rs` (`AppError` → `{error:{code,message}}` Türkçe; 5xx detayı yalnızca logda), `state.rs`, `routes/` (health: DB yoksa 503). Açılışta DB/ayar hatasında anlaşılır log + çıkış kodu 1. Elle test: 4 senaryo geçti.
|
||||||
|
- **Adım 2:** `db/` modülü: `col` sabitleri, `migrations.rs` (`schema_migrations`, v1 = koleksiyonları oluştur), `indexes.rs` (DATA_MODEL §4 + tüm hane koleksiyonlarına `(household_id, updated_at)`). `models/common.rs`: UUID v7, `new_meta`, `scope`, `touch`. Açılışta `db::prepare`. 4 birim testi; idempotency ve tek açık dönem indeksi elle doğrulandı. `uuid` (v7) eklendi.
|
||||||
|
|||||||
+2
-2
@@ -26,7 +26,7 @@
|
|||||||
|
|
||||||
## Faz 1 Adımları
|
## Faz 1 Adımları
|
||||||
- ✅ 1. `config.rs` + `db.rs` (MongoDB bağlantısı), Türkçe hata yapısı, health check'e DB durumu
|
- ✅ 1. `config.rs` + `db.rs` (MongoDB bağlantısı), Türkçe hata yapısı, health check'e DB durumu
|
||||||
- 🔄 2. Modeller, migration mekanizması ve indeksler
|
- ✅ 2. Modeller, migration mekanizması ve indeksler
|
||||||
- ⏳ 3. Kimlik doğrulama: kayıt (+ hane + örnek kategori/ödeme türü), giriş, çıkış, `me`; argon2 + JWT cookie + rate limit
|
- 🔄 3. Kimlik doğrulama: kayıt (+ hane + örnek kategori/ödeme türü), giriş, çıkış, `me`; argon2 + JWT cookie + rate limit
|
||||||
- ⏳ 4. Hane izolasyonu (middleware), üye davet kodu
|
- ⏳ 4. Hane izolasyonu (middleware), üye davet kodu
|
||||||
- ⏳ 5. Kategori ve ödeme türü uç noktaları + hane izolasyonu testleri, `api.http`
|
- ⏳ 5. Kategori ve ödeme türü uç noktaları + hane izolasyonu testleri, `api.http`
|
||||||
|
|||||||
Reference in New Issue
Block a user