feat(auth): kayıt, giriş, çıkış ve oturum (argon2 + JWT httpOnly cookie)

- Kayıt: kullanıcı + hane + örnek kategori/ödeme türü tek transaction'da
- Giriş: beni hatırla (30 gün / oturum), e-posta başına deneme sınırı
- AuthUser extractor: cookie'deki JWT'yi doğrular
- ValidJson: JSON + doğrulama, Türkçe hata mesajları
- api.http: auth istekleri eklendi
- Birim testleri: şifre, JWT, deneme sınırı, seed (11 test)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-11 23:37:52 +03:00
parent 4103b90e4b
commit d6136959b1
27 changed files with 1573 additions and 32 deletions
+75
View File
@@ -0,0 +1,75 @@
//! Yeni hane için örnek alt kategoriler ve ödeme türleri (DATA_MODEL.md §3.5, §3.6).
use mongodb::bson::{Document, Uuid};
use crate::models::common::new_meta;
/// (grup, ad, emoji)
const CATEGORIES: &[(&str, &str, &str)] = &[
("needs", "Market", "🛒"),
("needs", "Kira", "🏠"),
("needs", "Faturalar", "💡"),
("needs", "Ulaşım", "🚌"),
("wants", "Dışarıda yemek", "🍽️"),
("wants", "Giyim", "👕"),
("wants", "Eğlence", "🎉"),
("culture", "Kitap", "📖"),
("culture", "Eğitim", "🎓"),
("culture", "Sinema/Konser", "🎬"),
("unexpected", "Sağlık", "💊"),
("unexpected", "Tamir", "🔧"),
("unexpected", "Hediye", "🎁"),
];
/// (ad, emoji)
const PAYMENT_METHODS: &[(&str, &str)] = &[
("Nakit", "💵"),
("Kredi Kartı", "💳"),
("Banka Kartı", "🏧"),
("Havale/EFT", "🔁"),
];
pub fn categories(household_id: Uuid) -> Vec<Document> {
CATEGORIES
.iter()
.enumerate()
.map(|(i, (group, name, emoji))| {
let mut d = new_meta(household_id);
d.insert("group", *group);
d.insert("name", *name);
d.insert("emoji", *emoji);
d.insert("sort_order", (i as i32 + 1) * 10);
d.insert("is_active", true);
d
})
.collect()
}
pub fn payment_methods(household_id: Uuid) -> Vec<Document> {
PAYMENT_METHODS
.iter()
.enumerate()
.map(|(i, (name, emoji))| {
let mut d = new_meta(household_id);
d.insert("name", *name);
d.insert("emoji", *emoji);
d.insert("sort_order", (i as i32 + 1) * 10);
d.insert("is_active", true);
d
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::models::common::new_id;
#[test]
fn every_group_has_categories() {
let docs = categories(new_id());
for g in ["needs", "wants", "culture", "unexpected"] {
assert!(docs.iter().any(|d| d.get_str("group").unwrap() == g));
}
}
}