Add launcher engine (scopenet-core) and admin panel server
- scopenet-shared: wire types for branding, auth, instances, manifests - scopenet-core: Mojang versions/libraries/assets, automatic Java runtimes, Fabric/Quilt/Forge/NeoForge installation, file sync, launch command builder, Microsoft auth, servers.dat injection, server list ping - scopenet-panel: Axum + SQLite admin API with JWT auth, users/groups, branding, settings, instances, Modrinth/CurseForge/zip import, file hosting and launch stats Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011ARcGWxLx21FwXJ3yfGriS
This commit is contained in:
43 files changed
+9317
No files matched your search
@@ -0,0 +1,74 @@
|
||||
use axum::http::StatusCode;
|
||||
use axum::response::{IntoResponse, Response};
|
||||
use axum::Json;
|
||||
use serde_json::json;
|
||||
|
||||
/// Every handler returns `Result<_, AppError>`; errors become
|
||||
/// `{"error": "..."}` with a sensible status code.
|
||||
#[derive(Debug)]
|
||||
pub struct AppError {
|
||||
pub status: StatusCode,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
pub type AppResult<T> = Result<T, AppError>;
|
||||
|
||||
impl AppError {
|
||||
pub fn new(status: StatusCode, message: impl Into<String>) -> Self {
|
||||
Self { status, message: message.into() }
|
||||
}
|
||||
pub fn bad_request(m: impl Into<String>) -> Self {
|
||||
Self::new(StatusCode::BAD_REQUEST, m)
|
||||
}
|
||||
pub fn unauthorized(m: impl Into<String>) -> Self {
|
||||
Self::new(StatusCode::UNAUTHORIZED, m)
|
||||
}
|
||||
pub fn forbidden(m: impl Into<String>) -> Self {
|
||||
Self::new(StatusCode::FORBIDDEN, m)
|
||||
}
|
||||
pub fn not_found(m: impl Into<String>) -> Self {
|
||||
Self::new(StatusCode::NOT_FOUND, m)
|
||||
}
|
||||
pub fn conflict(m: impl Into<String>) -> Self {
|
||||
Self::new(StatusCode::CONFLICT, m)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoResponse for AppError {
|
||||
fn into_response(self) -> Response {
|
||||
if self.status.is_server_error() {
|
||||
tracing::error!("{}", self.message);
|
||||
}
|
||||
(self.status, Json(json!({ "error": self.message }))).into_response()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<anyhow::Error> for AppError {
|
||||
fn from(e: anyhow::Error) -> Self {
|
||||
Self::new(StatusCode::BAD_GATEWAY, format!("{e:#}"))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<sqlx::Error> for AppError {
|
||||
fn from(e: sqlx::Error) -> Self {
|
||||
Self::new(StatusCode::INTERNAL_SERVER_ERROR, format!("database error: {e}"))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for AppError {
|
||||
fn from(e: std::io::Error) -> Self {
|
||||
Self::new(StatusCode::INTERNAL_SERVER_ERROR, format!("io error: {e}"))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<serde_json::Error> for AppError {
|
||||
fn from(e: serde_json::Error) -> Self {
|
||||
Self::bad_request(format!("invalid JSON: {e}"))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<axum::extract::multipart::MultipartError> for AppError {
|
||||
fn from(e: axum::extract::multipart::MultipartError) -> Self {
|
||||
Self::bad_request(format!("upload failed: {e}"))
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user