mirror of
https://github.com/R0m1k3/podcastic.git
synced 2026-10-19 21:18:03 +02:00
Replace the Node/Express backend with a Rust implementation that keeps the same REST API, MongoDB collections and Redis cache keys, so existing users, subscriptions and progress keep working. - axum + mongodb + redis, bcrypt and HS256 JWT compatible with old data - RSS parsing, iTunes discovery/trending, background sync (1h / 4h) - Docker image now builds a static Rust binary on debian-slim - CI runs cargo tests before publishing the image - compose uses pull_policy: always so a restart picks up the new image Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
67 lines
1.7 KiB
Rust
67 lines
1.7 KiB
Rust
use axum::{
|
|
http::StatusCode,
|
|
response::{IntoResponse, Response},
|
|
Json,
|
|
};
|
|
use serde_json::{json, Value};
|
|
|
|
/// API error rendered as a JSON body (`{ "message": ... }` by default).
|
|
#[derive(Debug)]
|
|
pub struct AppError {
|
|
pub status: StatusCode,
|
|
pub body: Value,
|
|
}
|
|
|
|
impl AppError {
|
|
pub fn new(status: StatusCode, message: impl Into<String>) -> Self {
|
|
Self {
|
|
status,
|
|
body: json!({ "message": message.into() }),
|
|
}
|
|
}
|
|
|
|
pub fn with_body(status: StatusCode, body: Value) -> Self {
|
|
Self { status, body }
|
|
}
|
|
|
|
pub fn bad_request(message: impl Into<String>) -> Self {
|
|
Self::new(StatusCode::BAD_REQUEST, message)
|
|
}
|
|
|
|
pub fn unauthorized(message: impl Into<String>) -> Self {
|
|
Self::new(StatusCode::UNAUTHORIZED, message)
|
|
}
|
|
|
|
pub fn not_found(message: impl Into<String>) -> Self {
|
|
Self::new(StatusCode::NOT_FOUND, message)
|
|
}
|
|
|
|
pub fn validation(errors: Vec<Value>) -> Self {
|
|
Self::with_body(
|
|
StatusCode::BAD_REQUEST,
|
|
json!({ "message": "Validation error", "errors": errors }),
|
|
)
|
|
}
|
|
}
|
|
|
|
impl IntoResponse for AppError {
|
|
fn into_response(self) -> Response {
|
|
(self.status, Json(self.body)).into_response()
|
|
}
|
|
}
|
|
|
|
pub type ApiResult<T = Json<Value>> = Result<T, AppError>;
|
|
|
|
/// Logs the underlying error and maps it to a 500 carrying `message`.
|
|
pub fn internal<E: std::fmt::Display>(message: &'static str) -> impl FnOnce(E) -> AppError {
|
|
move |err| {
|
|
tracing::error!("{message}: {err}");
|
|
AppError::new(StatusCode::INTERNAL_SERVER_ERROR, message)
|
|
}
|
|
}
|
|
|
|
/// Builds a zod-like validation issue.
|
|
pub fn issue(field: &str, message: &str) -> Value {
|
|
json!({ "path": [field], "message": message })
|
|
}
|