feat(dağıtım): Docker imajı ve Axum'un arayüzü sunması

- Çok aşamalı Dockerfile: Node (arayüz) → Rust (release) → debian-slim, root olmayan kullanıcı
- Axum, STATIC_DIR tanımlıysa derlenmiş arayüzü sunar (SPA yönlendirmesi)
- Önbellek başlıkları (HTML no-cache, /assets 1 yıl), gzip/brotli sıkıştırma, istek logları
- Bilinmeyen /api/... → JSON 404; SIGTERM'de düzgün kapanış
- HEALTHCHECK (/api/health), .dockerignore, docs/DEPLOY.md

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-12 01:26:34 +03:00
parent e188922c0f
commit ad90551c6d
14 changed files with 391 additions and 6 deletions
+11
View File
@@ -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
+4
View File
@@ -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=
+1
View File
@@ -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_<uuid>` 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ç.
+42
View File
@@ -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"]
+164
View File
@@ -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"
+1
View File
@@ -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"
+10 -1
View File
@@ -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<PathBuf>,
}
/// 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,
})
}
+28 -2
View File
@@ -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…");
}
+18 -2
View File
@@ -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)
}
+42
View File
@@ -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
}
+1
View File
@@ -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 }
+56
View File
@@ -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>@<host>: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.<alan-adı>` | `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).
+6 -1
View File
@@ -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.
+7
View File
@@ -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