Geoffrey-rs/geoffrey_api/src/commands/mod.rs

152 lines
5.4 KiB
Rust

use std::fmt::Debug;
use std::sync::Arc;
use serde::de::DeserializeOwned;
use serde::Serialize;
use warp::filters::BoxedFilter;
use warp::Filter;
use geoffrey_models::models::parameters::{CommandRequest, GeoffreyParam};
use geoffrey_models::models::player::Player;
use geoffrey_models::models::response::api_error::GeoffreyAPIError;
use geoffrey_models::models::response::APIResponse;
use geoffrey_models::models::settings::GeoffreySettings;
use geoffrey_models::models::token::{Permissions, Token};
use geoffrey_models::models::CommandLevel;
use crate::api_endpoint::{ApiEndpoint, RequestType};
use crate::commands::add_item::AddItem;
use crate::commands::add_location::AddLocation;
use crate::commands::delete::Delete;
use crate::commands::edit::Edit;
use crate::commands::find::FindCommand;
use crate::commands::info::InfoCommand;
use crate::commands::link::LinkCommand;
use crate::commands::register::Register;
use crate::commands::remove_item::RemoveItem;
use crate::commands::report_out_of_stock::ReportOutOfStock;
use crate::commands::restock::Restock;
use crate::commands::selling::Selling;
use crate::commands::set_portal::SetPortal;
use crate::context::Context;
use crate::helper::{get_player_from_req, get_token_from_req};
use crate::Result;
pub mod add_item;
pub mod add_location;
pub mod add_token;
pub mod delete;
pub mod edit;
pub mod find;
pub mod info;
pub mod link;
pub mod register;
pub mod remove_item;
pub mod report_out_of_stock;
pub mod restock;
pub mod selling;
pub mod set_portal;
pub mod settings;
pub trait Command: ApiEndpoint {
type Req: GeoffreyParam + 'static;
type Resp: Serialize + DeserializeOwned + Send + Debug;
fn command_level() -> CommandLevel;
fn run_command(ctx: Arc<Context>, req: &Self::Req, user: Option<Player>) -> Result<Self::Resp>;
fn validate_parameters(_: &Self::Req, _: &GeoffreySettings) -> Result<()> {
Ok(())
}
fn user_is_authorized(token: &Option<Token>, user: &Option<Player>) -> Result<()> {
if let Some(token) = token {
if !match Self::command_level() {
CommandLevel::MOD => token.check_permission(Permissions::ModCommand),
CommandLevel::ADMIN => token.check_permission(Permissions::Admin),
_ => token.check_permission(Permissions::Command),
} {
return Err(GeoffreyAPIError::TokenNotAuthorized);
}
if Self::command_level() == CommandLevel::ALL {
Ok(())
} else if let Some(user) = user {
if user.auth_level >= Self::command_level() {
Ok(())
} else {
Err(GeoffreyAPIError::PermissionInsufficient)
}
} else {
Err(GeoffreyAPIError::PlayerNotRegistered)
}
} else {
Err(GeoffreyAPIError::TokenNotAuthorized)
}
}
}
pub fn handle_command<T: Command>(
ctx: Arc<Context>,
req: CommandRequest<T::Req>,
) -> Result<T::Resp> {
log::info!("Running command {}", T::endpoint_name());
log::debug!("User: {:?} Request params: {:?}", req.user_id, req.params);
let user = get_player_from_req::<T::Req>(&ctx.db, &req)?;
let token = get_token_from_req(&ctx.db, &req)?;
match T::user_is_authorized(&token, &user) {
Ok(_) => {
T::validate_parameters(&req.params, &ctx.cfg.geoffrey_settings)?;
T::run_command(ctx, &req.params, user)
}
Err(e) => Err(e),
}
}
#[allow(clippy::needless_return)]
pub fn create_command_filter<T: Command>(ctx: Arc<Context>) -> BoxedFilter<(impl warp::Reply,)> {
let filter = warp::path(T::endpoint_name())
.and(warp::any().map(move || ctx.clone()))
.and(warp::body::json())
.map(|ctx: Arc<Context>, req: CommandRequest<T::Req>| {
let reply = handle_command::<T>(ctx, req);
if let Ok(reply) = reply {
log::debug!("Successfully processed command");
warp::reply::json(&APIResponse::Response::<T::Resp>(reply))
} else {
let e = reply.err().unwrap();
let msg = e.to_string();
log::warn!("Got error when processing command '{:?}': {}", e, msg);
warp::reply::json(&APIResponse::<T::Resp>::Error { error: e, msg })
}
});
if T::request_type() == RequestType::POST {
return filter.and(warp::post()).boxed();
} else {
return filter.and(warp::get()).boxed();
}
}
pub fn command_filter(
ctx: Arc<Context>,
) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
warp::path("command").and(
create_command_filter::<FindCommand>(ctx.clone())
.or(create_command_filter::<AddLocation>(ctx.clone()))
.or(create_command_filter::<Register>(ctx.clone()))
.or(create_command_filter::<Selling>(ctx.clone()))
.or(create_command_filter::<AddItem>(ctx.clone()))
.or(create_command_filter::<Delete>(ctx.clone()))
.or(create_command_filter::<LinkCommand>(ctx.clone()))
.or(create_command_filter::<Edit>(ctx.clone()))
.or(create_command_filter::<RemoveItem>(ctx.clone()))
.or(create_command_filter::<InfoCommand>(ctx.clone()))
.or(create_command_filter::<Restock>(ctx.clone()))
.or(create_command_filter::<ReportOutOfStock>(ctx.clone()))
.or(create_command_filter::<SetPortal>(ctx)),
)
}