mirror of
https://gitea.tendokyu.moe/ppc/amnet.git
synced 2026-09-22 22:28:22 +03:00
100 lines
3.3 KiB
Rust
100 lines
3.3 KiB
Rust
use std::time::{Duration, Instant};
|
|
|
|
use axum::http::{StatusCode, header};
|
|
use axum::response::{IntoResponse, Response};
|
|
use axum::routing::{get, post};
|
|
use axum::{Json, Router};
|
|
use tower::ServiceBuilder;
|
|
use tower_http::{cors::{Any, CorsLayer}, trace::TraceLayer};
|
|
|
|
use crate::card::{CardPresentResult, CardPresenter};
|
|
use crate::config::Config;
|
|
use crate::metrics::SystemMetrics;
|
|
use crate::utils;
|
|
use crate::webapi::API_VERSION;
|
|
|
|
pub fn build_server() -> Router {
|
|
let services = ServiceBuilder::new()
|
|
.layer(TraceLayer::new_for_http())
|
|
.layer(CorsLayer::new().allow_origin(Any).allow_methods(Any));
|
|
|
|
Router::<()>::new()
|
|
.route("/amnet/info", get(server_state_handler))
|
|
.route("/amnet/signin", post(card_submission_handler))
|
|
.layer(services)
|
|
}
|
|
|
|
fn build_error_response(status: StatusCode, message: &str) -> Response {
|
|
let error = crate::webapi::ServerError {
|
|
error: message.to_string(),
|
|
};
|
|
|
|
(status, Json(error)).into_response()
|
|
}
|
|
|
|
async fn server_state_handler() -> Json<crate::webapi::ServerState> {
|
|
let config = Config::instance();
|
|
let metrics = SystemMetrics::instance();
|
|
|
|
let session_uptime = metrics.get_server_uptime().map(|d| d.as_secs()).unwrap_or(0);
|
|
let time_since_last_poll = metrics.get_last_poll().map(|d| d.as_secs());
|
|
|
|
let state = crate::webapi::ServerState {
|
|
api_version: API_VERSION,
|
|
game_id: config.game_id.clone(),
|
|
server_name: config.server_name.clone(),
|
|
session_uptime,
|
|
time_since_last_poll,
|
|
};
|
|
|
|
Json(state)
|
|
}
|
|
|
|
async fn card_submission_handler(
|
|
Json(payload): Json<crate::webapi::CardSubmissionRequest>,
|
|
) -> impl IntoResponse {
|
|
if payload.access_code.len() > 20 || payload.access_code.len() == 0 {
|
|
return build_error_response(StatusCode::BAD_REQUEST, "Access code is required");
|
|
}
|
|
|
|
let Some(access_code) = utils::parse_access_code(&payload.access_code) else {
|
|
return build_error_response(StatusCode::UNPROCESSABLE_ENTITY, "Invalid access code provided");
|
|
};
|
|
|
|
let card_idm_bytes = if let Some(idm_hex) = payload.card_idm_hex.as_ref() {
|
|
if idm_hex.len() != 16 {
|
|
return build_error_response(StatusCode::BAD_REQUEST, "IDm must be a 16-char hex string");
|
|
}
|
|
|
|
let Some(idm_bytes) = utils::parse_idm_hex(idm_hex) else {
|
|
return build_error_response(StatusCode::UNPROCESSABLE_ENTITY, "Invalid IDm provided");
|
|
};
|
|
|
|
Some(idm_bytes)
|
|
} else {
|
|
None
|
|
};
|
|
|
|
match CardPresenter::instance().present_card(access_code, card_idm_bytes, Duration::from_secs(5)) {
|
|
CardPresentResult::Accepted => StatusCode::ACCEPTED.into_response(),
|
|
CardPresentResult::SlotUnavailable(release_time) => {
|
|
let now = Instant::now();
|
|
let wait_secs = if release_time > now {
|
|
(release_time - now).as_secs()
|
|
} else {
|
|
0
|
|
};
|
|
|
|
let mut response = build_error_response(
|
|
StatusCode::TOO_MANY_REQUESTS,
|
|
"A card has already been submitted. Please wait before submitting another.",
|
|
);
|
|
|
|
let headers = response.headers_mut();
|
|
headers.insert(header::RETRY_AFTER, wait_secs.to_string().parse().unwrap());
|
|
|
|
response
|
|
}
|
|
}
|
|
}
|