diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..b987e71 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,11 @@ +# Docker build bağlamına girmeyecekler (hız + gizli bilgi sızmasın) +.git +.env +.env.* +!.env.example +**/node_modules +**/target +**/dist +backups/ +.vscode/ +*.log diff --git a/.env.example b/.env.example index 550861a..bed03ef 100644 --- a/.env.example +++ b/.env.example @@ -20,3 +20,7 @@ PORT=8080 # Log seviyesi RUST_LOG=kakeibo=debug,tower_http=info + +# Derlenmiş arayüz klasörü. Geliştirmede BOŞ bırakın (arayüzü Vite sunar). +# Docker imajında /app/static olarak ayarlıdır. +# STATIC_DIR= diff --git a/CLAUDE.md b/CLAUDE.md index 70cad68..c5bffc5 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -72,6 +72,7 @@ npm run icons # frontend/brand/*.svg → favicon, PWA, Apple, Tauri ikonl - Formlar: react-hook-form + zod (`mode: 'onTouched'`), alanlar `components/form-field.tsx` (`FormField`, `PasswordInput`, `FormError`); sunucu hatası `setError` ile ilgili alana. Bildirim: `toast` (sonner, `components/toaster.tsx`). - Grup tanımları: `lib/groups.ts`; tema: `lib/theme.tsx` (+ `index.html` içindeki ilk-boya betiği); biçimlendirme: `lib/format.ts`. - API örnekleri: `backend/api.http` +- Canlı: `Dockerfile` (tek imaj), `docs/DEPLOY.md` (ortam değişkenleri, Dokploy, MongoDB, yedek). `routes::app()` = API + `STATIC_DIR` varsa SPA (`routes/spa.rs`) + sıkıştırma + TraceLayer. Testler `routes::router()` kullanır. - Backend = lib (`src/lib.rs`) + bin (`src/main.rs`). Entegrasyon testleri `backend/tests/` (her test geçici `kakeibo_test_` DB, `tests/common` yardımcıları). - Kayıt politikası: `REGISTRATION=invite_only` (varsayılan; ilk hane serbest, sonra yalnızca davet) | `open`. Backend'de zorunlu (403 `registration_closed`), `GET /api/auth/registration` arayüze bildirir. Testler `TestApp::new()` = open, `with_registration(..)`. - Biçim: Prettier (`frontend/.prettierrc.json`: tek tırnak, `;` yok, 100 sütun); `npm --prefix frontend run format`. `src/types/` ve `components/ui/` hariç. diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..c6f1e99 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,42 @@ +# Kakeibo — canlı ortam imajı (tek servis: Axum hem /api'yi hem arayüzü sunar) +# Yerelde deneme: docker build -t kakeibo . && docker run --rm -p 8090:8080 --env-file .env kakeibo + +# ─── 1) Arayüz (React + Vite) ───────────────────────────────────────────── +FROM node:26-alpine AS web +WORKDIR /app/frontend +# Önce yalnızca paket dosyaları → bağımlılık katmanı önbellekte kalır +COPY frontend/package.json frontend/package-lock.json ./ +RUN npm ci --no-audit --no-fund +COPY frontend/ ./ +RUN npm run build + +# ─── 2) Backend (Rust, release) ─────────────────────────────────────────── +FROM rust:1.99-slim-trixie AS api +WORKDIR /app/backend +# Bağımlılıkları ayrı katmanda derle (kod değişince yeniden derlenmez) +COPY backend/Cargo.toml backend/Cargo.lock ./ +RUN mkdir src && echo 'fn main() {}' > src/main.rs && touch src/lib.rs \ + && cargo build --release --locked \ + && rm -rf src target/release/deps/*kakeibo* target/release/.fingerprint/kakeibo-* target/release/kakeibo* +COPY backend/src ./src +# touch: sahte lib.rs/main.rs'ten daha yeni olsun → Cargo gerçek kodu kesinlikle yeniden derler +RUN touch src/lib.rs src/main.rs && cargo build --release --locked + +# ─── 3) Çalışma imajı (küçük, root olmayan kullanıcı) ───────────────────── +FROM debian:trixie-slim +RUN apt-get update \ + && apt-get install -y --no-install-recommends ca-certificates curl \ + && rm -rf /var/lib/apt/lists/* \ + && useradd --system --uid 10001 --no-create-home kakeibo +COPY --from=api /app/backend/target/release/kakeibo /usr/local/bin/kakeibo +COPY --from=web /app/frontend/dist /app/static + +ENV STATIC_DIR=/app/static \ + PORT=8080 \ + RUST_LOG=kakeibo=info,tower_http=info +EXPOSE 8080 +USER kakeibo +# Dokploy/Docker sağlık kontrolü (uygulama + veritabanı) +HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \ + CMD curl -fsS http://localhost:${PORT}/api/health || exit 1 +CMD ["kakeibo"] diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 29484da..24d984f 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + [[package]] name = "ahash" version = "0.8.12" @@ -24,6 +30,21 @@ dependencies = [ "memchr", ] +[[package]] +name = "alloc-no-stdlib" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2fb6cfd47bf496ff64095c20eaba0c201404ee38714d4142fcfa1dc334fcc7a" + +[[package]] +name = "alloc-stdlib" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5c1865780388bfa186411ab5f247819487fc4864c6e9c3106611fa347586e1" +dependencies = [ + "alloc-no-stdlib", +] + [[package]] name = "argon2" version = "0.6.0" @@ -36,6 +57,18 @@ dependencies = [ "password-hash", ] +[[package]] +name = "async-compression" +version = "0.4.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ad47a740fb906fffdd891e8b6d5fde2511600f7bfaebd68939fa08a14999459" +dependencies = [ + "compression-codecs", + "compression-core", + "pin-project-lite", + "tokio", +] + [[package]] name = "async-trait" version = "0.1.92" @@ -196,6 +229,27 @@ dependencies = [ "hybrid-array", ] +[[package]] +name = "brotli" +version = "9.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "627b7cfb0578f0eafce087a90fcd40add1231900b69e4d4de2d456260a4f5d81" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "6.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75c74c9c51b9140663e196b5fb88bc94c334d7a497c8311cb759d8557874747f" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + [[package]] name = "bson" version = "2.15.0" @@ -274,6 +328,24 @@ dependencies = [ "memchr", ] +[[package]] +name = "compression-codecs" +version = "0.4.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84f840cff84d10085260e460023934d0af699e71212b09b2a41452e8bd9cda43" +dependencies = [ + "brotli", + "compression-core", + "flate2", + "memchr", +] + +[[package]] +name = "compression-core" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ebdda89edc534bf8d5dd2857562633b6495557e5846552ade994b99ed13c36a" + [[package]] name = "const-oid" version = "0.9.6" @@ -351,6 +423,15 @@ dependencies = [ "libc", ] +[[package]] +name = "crc32fast" +version = "1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b929dd2a135771f40afabd1073f85eed59c800b7c61901e5c254270497dad64" +dependencies = [ + "cfg-if", +] + [[package]] name = "critical-section" version = "1.2.0" @@ -711,6 +792,17 @@ version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c66915cc5391097de7a2b0b972e8505aa33c0df401fd3a6811dbdb32881ba48" +[[package]] +name = "flate2" +version = "1.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "626d4928326b646e70f65ed36fc7cf9f206bdb68a0019ee7c6b4709da4e084c4" +dependencies = [ + "crc32fast", + "miniz_oxide", + "zlib-rs", +] + [[package]] name = "foldhash" version = "0.2.0" @@ -994,6 +1086,12 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "http-range-header" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c" + [[package]] name = "httparse" version = "1.10.1" @@ -1296,6 +1394,7 @@ dependencies = [ "time", "tokio", "tower", + "tower-http", "tracing", "tracing-subscriber", "ts-rs", @@ -1430,6 +1529,26 @@ version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5848f5a9e8117cf94f2f90faac24a87ff81600ff8628c1e3deaab572e76bfa1" +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "miniz_oxide" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c" +dependencies = [ + "adler2", + "simd-adler32", +] + [[package]] name = "mio" version = "1.2.4" @@ -2269,6 +2388,12 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "simd-adler32" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" + [[package]] name = "simd_cesu8" version = "1.2.0" @@ -2592,6 +2717,33 @@ dependencies = [ "tracing", ] +[[package]] +name = "tower-http" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08a05a66a4fdd61cbbe0a1d755ffe0ca6aba159dd4820936a0ff8a8278245b9c" +dependencies = [ + "async-compression", + "bitflags", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "http-range-header", + "httpdate", + "mime", + "mime_guess", + "percent-encoding", + "pin-project-lite", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "tower-layer" version = "0.3.3" @@ -2714,6 +2866,12 @@ version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" +[[package]] +name = "unicase" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357cc3acc6a036009fd6c973ed009037c732d60d0b4f6c673e9041497482a28f" + [[package]] name = "unicode-bidi" version = "0.3.18" @@ -3174,6 +3332,12 @@ dependencies = [ "syn 3.0.10", ] +[[package]] +name = "zlib-rs" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b268e58e7c693d7c271f93ffc4ba3b380412554231c85bf61ca7af91042a4112" + [[package]] name = "zmij" version = "1.0.23" diff --git a/backend/Cargo.toml b/backend/Cargo.toml index d80992e..bd1d46f 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -15,6 +15,7 @@ serde_json = "1.0.154" thiserror = "2.0.21" time = "0.3.55" tokio = { version = "1.54.1", features = ["full"] } +tower-http = { version = "0.7.1", features = ["fs", "compression-gzip", "compression-br", "trace"], default-features = false } tracing = "0.1.44" tracing-subscriber = { version = "0.3.23", features = ["env-filter"] } ts-rs = "12.0.1" diff --git a/backend/src/config.rs b/backend/src/config.rs index a1a18e2..d7dfd4e 100644 --- a/backend/src/config.rs +++ b/backend/src/config.rs @@ -1,6 +1,6 @@ //! Ortam değişkenlerinden uygulama ayarlarını okur. -use std::env; +use std::{env, path::PathBuf}; #[derive(Debug, Clone)] pub struct Config { @@ -9,6 +9,9 @@ pub struct Config { pub app_url: String, pub port: u16, pub registration: RegistrationMode, + /// Derlenmiş frontend klasörü (`STATIC_DIR`). Tanımlıysa Axum arayüzü de sunar + /// (canlıda tek servis). Geliştirmede boş: arayüzü Vite sunar. + pub static_dir: Option, } /// Yeni hane kaydı politikası (`REGISTRATION` ortam değişkeni). @@ -54,6 +57,11 @@ impl Config { } }; + let static_dir = env::var("STATIC_DIR") + .ok() + .filter(|v| !v.trim().is_empty()) + .map(PathBuf::from); + let port = match env::var("PORT") { Ok(p) => p .parse() @@ -67,6 +75,7 @@ impl Config { app_url, port, registration, + static_dir, }) } diff --git a/backend/src/main.rs b/backend/src/main.rs index 9b3a3e5..2db179c 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -43,7 +43,7 @@ async fn main() -> ExitCode { let port = config.port; let state = AppState::new(db, config); - let app = routes::router(state); + let app = routes::app(state); let addr = SocketAddr::from(([0, 0, 0, 0], port)); let listener = match tokio::net::TcpListener::bind(addr).await { @@ -57,9 +57,35 @@ async fn main() -> ExitCode { }; tracing::info!("Kakeibo API çalışıyor: http://localhost:{port}"); - if let Err(e) = axum::serve(listener, app).await { + if let Err(e) = axum::serve(listener, app) + .with_graceful_shutdown(shutdown_signal()) + .await + { tracing::error!("Sunucu hatası: {e}"); return ExitCode::FAILURE; } ExitCode::SUCCESS } + +/// Docker/Dokploy durdururken (SIGTERM) veya Ctrl+C'de: yeni istek almayı bırak, +/// süren istekleri bitir, sonra kapan (yarım kalan yazma olmasın). +async fn shutdown_signal() { + let ctrl_c = async { + let _ = tokio::signal::ctrl_c().await; + }; + #[cfg(unix)] + let terminate = async { + if let Ok(mut s) = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate()) + { + s.recv().await; + } + }; + #[cfg(not(unix))] + let terminate = std::future::pending::<()>(); + + tokio::select! { + _ = ctrl_c => {}, + _ = terminate => {}, + } + tracing::info!("Kapatma sinyali alındı, sunucu kapanıyor…"); +} diff --git a/backend/src/routes/mod.rs b/backend/src/routes/mod.rs index 6a6e156..af3f77a 100644 --- a/backend/src/routes/mod.rs +++ b/backend/src/routes/mod.rs @@ -4,13 +4,27 @@ mod auth; mod catalog; mod health; mod household; +mod spa; use axum::{ Router, routing::{delete, get, patch, post, put}, }; +use tower_http::{compression::CompressionLayer, trace::TraceLayer}; -use crate::state::AppState; +use crate::{error::AppError, state::AppState}; + +/// Sunucunun tamamı: API + (STATIC_DIR tanımlıysa) arayüz dosyaları, +/// sıkıştırma ve istek logları. `main.rs` bunu kullanır. +pub fn app(state: AppState) -> Router { + let static_dir = state.config.static_dir.clone(); + let mut app = router(state); + if let Some(dir) = static_dir { + app = app.fallback_service(spa::router(&dir)); + } + app.layer(CompressionLayer::new()) + .layer(TraceLayer::new_for_http()) +} /// Tüm API route'ları. Testler de bu fonksiyonu kullanır. pub fn router(state: AppState) -> Router { @@ -44,7 +58,9 @@ pub fn router(state: AppState) -> Router { .route( "/payment-methods/{id}", patch(catalog::update_payment_method), - ); + ) + // Bilinmeyen /api/... → JSON 404 (arayüzün index.html'i dönmesin) + .fallback(|| async { AppError::NotFound }); Router::new().nest("/api", api).with_state(state) } diff --git a/backend/src/routes/spa.rs b/backend/src/routes/spa.rs new file mode 100644 index 0000000..accbba4 --- /dev/null +++ b/backend/src/routes/spa.rs @@ -0,0 +1,42 @@ +//! Derlenmiş arayüzün (frontend/dist) sunulması — tek sayfa uygulama (SPA). +//! +//! - `/assets/*` : adları içerik özetli (hash) → 1 yıl önbellek, `immutable` +//! - diğer dosyalar (favicon, ikonlar): 1 saat önbellek +//! - olmayan yol (`/defter`, `/plan` ...) → `index.html` (yönlendirmeyi React Router yapar) +//! - HTML hiç önbelleğe alınmaz → yeni sürüm yayınlanınca hemen yüklenir + +use std::path::Path; + +use axum::{ + Router, + extract::Request, + http::{HeaderValue, header}, + middleware::{self, Next}, + response::Response, +}; +use tower_http::services::{ServeDir, ServeFile}; + +pub fn router(dir: &Path) -> Router { + let files = ServeDir::new(dir).fallback(ServeFile::new(dir.join("index.html"))); + Router::new() + .fallback_service(files) + .layer(middleware::from_fn(cache_headers)) +} + +async fn cache_headers(req: Request, next: Next) -> Response { + let is_asset = req.uri().path().starts_with("/assets/"); + let mut res = next.run(req).await; + + let is_html = res + .headers() + .get(header::CONTENT_TYPE) + .is_some_and(|v| v.as_bytes().starts_with(b"text/html")); + let policy = match (is_html, is_asset) { + (true, _) => "no-cache", + (false, true) => "public, max-age=31536000, immutable", + (false, false) => "public, max-age=3600", + }; + res.headers_mut() + .insert(header::CACHE_CONTROL, HeaderValue::from_static(policy)); + res +} diff --git a/backend/tests/common/mod.rs b/backend/tests/common/mod.rs index 346d249..fb7db7c 100644 --- a/backend/tests/common/mod.rs +++ b/backend/tests/common/mod.rs @@ -56,6 +56,7 @@ impl TestApp { app_url: "http://localhost".into(), port: 0, registration, + static_dir: None, }; let router = routes::router(AppState::new(db.clone(), config)); Self { router, db } diff --git a/docs/DEPLOY.md b/docs/DEPLOY.md new file mode 100644 index 0000000..de4fd04 --- /dev/null +++ b/docs/DEPLOY.md @@ -0,0 +1,56 @@ +# 🚀 Canlıya Alma (Atlas Homeserver + Dokploy) + +> Durum: Faz 3 — Adım 1 ✅ (Docker imajı) · Adım 2–5 devam ediyor + +## 1. Mimari + +``` +İnternet ──HTTPS──▶ Traefik (Dokploy) ──▶ kakeibo konteyneri :8080 ──▶ MongoDB (rs0) + ├─ /api/* → Rust (Axum) API + └─ diğer → derlenmiş arayüz (React) +``` + +- **Tek imaj, tek servis, tek alan adı** → CORS yok, cookie aynı origin'de. +- İmaj: çok aşamalı `Dockerfile` (Node → arayüz derlemesi, Rust → release derleme, `debian:trixie-slim` çalışma imajı, root olmayan kullanıcı `kakeibo`, ~175 MB). +- Sağlık kontrolü: `GET /api/health` → `200 {"status":"ok","db":"ok"}`; veritabanı yoksa `503`. +- Kapanış: `SIGTERM` gelince süren istekler bitirilir, sonra kapanır. + +## 2. Ortam değişkenleri + +| Değişken | Zorunlu | Örnek / varsayılan | Açıklama | +|---|---|---|---| +| `MONGODB_URI` | ✅ | `mongodb://kakeibo_app:<şifre>@:27017/kakeibo?authSource=kakeibo&replicaSet=rs0` | Ayrı, yalnızca `kakeibo` DB'sinde `readWrite` yetkili kullanıcı | +| `JWT_SECRET` | ✅ | `openssl rand -hex 32` çıktısı | En az 32 karakter. **Değişirse tüm oturumlar kapanır** | +| `APP_URL` | ✅ | `https://kakeibo.` | `https://` ile başlarsa cookie `Secure` olur | +| `REGISTRATION` | – | `invite_only` | `invite_only`: ilk hane serbest, sonra yalnızca davet · `open`: herkes | +| `RUST_LOG` | – | `kakeibo=info,tower_http=info` | Log seviyesi | +| `PORT` | – | `8080` | İmajda ayarlı, değiştirmeyin | +| `STATIC_DIR` | – | `/app/static` | İmajda ayarlı, değiştirmeyin | + +## 3. Yerelde imajı deneme + +```bash +docker build -t kakeibo . +# Yerel MongoDB (27020) ile, geliştirme sunucusuyla çakışmasın diye 8090'da: +docker run --rm --network host --env-file .env -e PORT=8090 kakeibo +# → http://localhost:8090 +``` + +Kontrol listesi (Faz 3 – Adım 1'de doğrulandı): +- [x] `/` ve `/defter` → `index.html` (`Cache-Control: no-cache`) +- [x] `/assets/*.js` → brotli/gzip, `max-age=31536000, immutable` +- [x] `/api/health` → `db: ok`; bilinmeyen `/api/...` → JSON 404 +- [x] Docker `HEALTHCHECK` → healthy; süreç `kakeibo` kullanıcısıyla çalışır +- [x] `docker stop` → düzgün kapanış logu + +## 4. Canlı MongoDB + +> Faz 3 – Adım 2'de birlikte doldurulacak (KAKEIBO_PROMPT §12.3). + +## 5. Dokploy kurulumu + +> Faz 3 – Adım 3'te birlikte doldurulacak. + +## 6. Yedekleme ve geri yükleme + +> Faz 3 – Adım 5'te doldurulacak (günlük `mongodump`, son 14 yedek). diff --git a/docs/MEMORY.md b/docs/MEMORY.md index c31cda8..5a8f8a0 100644 --- a/docs/MEMORY.md +++ b/docs/MEMORY.md @@ -1,7 +1,7 @@ # 🧠 Proje Hafızası ## ▶️ Sıradaki Adım -**Faz 3 – Başlangıç:** Faz 3 planını sun (canlıya alma: çok aşamalı Dockerfile, Axum'un frontend'i sunması + SPA yönlendirmesi, Dokploy, canlı MongoDB kontrolü — KAKEIBO_PROMPT §12.3 sorularını kullanıcıyla birlikte, domain + HTTPS, yedekleme). Sunucuya erişim/bilgi kullanıcıdan alınacak. +**Faz 3 – Adım 2:** Kullanıcıdan bilgi bekleniyor: alan adı, Dokploy sürümü/erişimi, Gitea'nın Dokploy'a bağlı olup olmadığı, MongoDB kurulum şekli (konteyner mi, doğrudan mı; Dokploy içinde mi), replica set durumu. Sonra §12.3 komutları (kullanıcı sunucuda çalıştırır). ## 📌 Alınan Kararlar - **Yerel demo hesapları** (`kakeibo_dev`): `demo@ornek.com` / `demo12345` (owner, "Demo Hanesi") · `es@ornek.com` / `demo12345` (member, aynı hane). `npm run db:reset` silerse API ile yeniden oluştur (ileride `db:seed`). @@ -33,6 +33,8 @@ - Yönlendirme sonrası toast için `mutateAsync` + await kullanıldı (mutate() callback'leri bileşen kalkınca çalışmayabilir). - **Kayıt politikası (kullanıcı kararı, 2026-11-12): Seçenek 1** → `REGISTRATION=invite_only` varsayılan; sistemde hiç hane yokken ilk kayıt yeni hane kurar (sunucu sahibi), sonra yalnızca davetle. `open` isteğe bağlı. Canlıda ilk hesabı kullanıcı `/kayit`'tan açar; demo hesaplar canlıya taşınmaz. - Prettier ayarı eklendi (önceden yoktu → VS Code kaydedince çift tırnağa çevirebilirdi). +- Canlı imaj: node:26-alpine → rust:1.99-slim-trixie → debian:trixie-slim (ca-certificates + curl, uid 10001 `kakeibo`), 174 MB. Docker katman önbelleği için sahte lib/main ile bağımlılık derlemesi; kalıntılar `*kakeibo*` + `.fingerprint` silinir, gerçek kaynak `touch`lanır (yoksa sahte lib kullanılıyordu). +- Önbellek: HTML `no-cache`, `/assets/*` 1 yıl immutable, diğer statik 1 saat. Bilinmeyen `/api/*` → JSON 404. - Sürümler: Rust 1.99.1 (edition 2024), Axum 0.8, React 19, Vite 8, TypeScript 7, Tailwind 4.3, shadcn 4.23, Vitest 5, react-router 8, TanStack Query 5. ## ❓ Açık Konular @@ -68,3 +70,6 @@ ### 2026-11-12 — Faz 3 öncesi - Kullanıcı `api.http`'deki kayıt örneğini (yeni@ornek.com) hesap sandı → dosya başlığına hazır hesaplar kutusu ve "bu bir KAYIT isteğidir" uyarısı eklendi. - Kayıt politikası: `config.rs` `RegistrationMode`, `AppError::RegistrationClosed` (403), `account::new_household_allowed`, `GET /api/auth/registration` (`RegistrationInfo` TS tipi). `tests/registration.rs` (2). Kayıt ekranı kapalıyken yalnızca davet modu + açıklama. `.env`/`.env.example`: `REGISTRATION=invite_only`. Prettier (`.prettierrc.json`, `.prettierignore`, `format` / `format:check`), tüm src biçimlendirildi (yalnızca boşluk). + +### 2026-11-12 — Faz 3 +- **Adım 1:** tower-http (fs, compression gzip/br, trace). `routes::app()` + `routes/spa.rs` (ServeDir + index.html fallback + cache middleware), API fallback JSON 404, `STATIC_DIR` config, graceful shutdown (SIGTERM/Ctrl+C). `Dockerfile`, `.dockerignore`, `docs/DEPLOY.md` başlatıldı. İmaj yerelde çalıştırıldı (host network, 8090): 10 kontrol + tarayıcı ekran görüntüsü geçti. İlk derlemede Docker önbellek hilesi hatası (sahte lib.rs) bulundu, düzeltildi. diff --git a/docs/PHASES.md b/docs/PHASES.md index fb20e3b..bfdb068 100644 --- a/docs/PHASES.md +++ b/docs/PHASES.md @@ -37,3 +37,10 @@ - ✅ 3. `lib/api.ts`, TanStack Query, ts-rs tip üretimi, oturum durumu - ✅ 4. Router (react-router), korumalı sayfalar, layout: mobil alt menü / masaüstü sol menü - ✅ 5. Giriş / kayıt ekranları (react-hook-form + zod, beni hatırla, davet kodu), arayüz metinleri tek dosyada + +## Faz 3 Adımları +- ✅ 1. Dockerfile (çok aşamalı), Axum arayüzü sunar (SPA yönlendirmesi, önbellek başlıkları, sıkıştırma), graceful shutdown, yerelde imaj testi +- ⏳ 2. Canlı MongoDB kontrolü (§12.3): kurulum şekli, replica set, ayrı kullanıcı, dış erişim +- ⏳ 3. Dokploy: Gitea bağlantısı, ortam değişkenleri, alan adı + HTTPS, ilk yayın +- ⏳ 4. Canlıda ilk hesap + kontrol listesi +- ⏳ 5. Yedekleme (mongodump, 14 gün) + geri yükleme denemesi