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:
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user