1
Fork 0
mirror of https://github.com/Steffo99/distributed-arcade.git synced 2024-10-16 06:27:30 +00:00

Create POST /board/

This commit is contained in:
Steffo 2022-11-11 02:11:18 +01:00
parent 4e56279442
commit 0148630122
Signed by: steffo
GPG key ID: 6965406171929D01
7 changed files with 291 additions and 49 deletions

View file

@ -1,6 +1,7 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="DuplicatedCode" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
<option name="processCode" value="true" />
<option name="processLiterals" value="true" />

113
Cargo.lock generated
View file

@ -13,6 +13,15 @@ dependencies = [
"version_check",
]
[[package]]
name = "aho-corasick"
version = "0.7.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e"
dependencies = [
"memchr",
]
[[package]]
name = "arc-swap"
version = "1.5.1"
@ -30,6 +39,17 @@ dependencies = [
"syn",
]
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"libc",
"winapi",
]
[[package]]
name = "autocfg"
version = "1.1.0"
@ -122,16 +142,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "338089f42c427b86394a5ee60ff321da23a5c89c9d89514c829687b26359fcff"
[[package]]
name = "distributed-arcade"
name = "distributed_arcade"
version = "0.1.0"
dependencies = [
"axum",
"lazy_static",
"log",
"pretty_env_logger",
"r2d2",
"redis",
"serde",
"serde_json",
"tokio",
]
[[package]]
name = "env_logger"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36"
dependencies = [
"atty",
"humantime",
"log",
"regex",
"termcolor",
]
[[package]]
name = "fnv"
version = "1.0.7"
@ -296,6 +333,15 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
[[package]]
name = "humantime"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f"
dependencies = [
"quick-error",
]
[[package]]
name = "hyper"
version = "0.14.23"
@ -479,6 +525,16 @@ version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "pretty_env_logger"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "926d36b9553851b8b0005f1275891b392ee4d2d833852c417ed025477350fb9d"
dependencies = [
"env_logger",
"log",
]
[[package]]
name = "proc-macro2"
version = "1.0.47"
@ -488,6 +544,12 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "quick-error"
version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
[[package]]
name = "quote"
version = "1.0.21"
@ -573,6 +635,23 @@ dependencies = [
"bitflags",
]
[[package]]
name = "regex"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
[[package]]
name = "ryu"
version = "1.0.11"
@ -599,6 +678,20 @@ name = "serde"
version = "1.0.147"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.147"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
@ -680,6 +773,15 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20518fe4a4c9acf048008599e464deb21beeae3d3578418951a189c235a7a9a8"
[[package]]
name = "termcolor"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
dependencies = [
"winapi-util",
]
[[package]]
name = "tinyvec"
version = "1.6.0"
@ -884,6 +986,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"

View file

@ -1,5 +1,5 @@
[package]
name = "distributed-arcade"
name = "distributed_arcade"
version = "0.1.0"
edition = "2021"
@ -11,3 +11,7 @@ axum = { version = "0.5.17" }
tokio = { version = "1.21.2", features=["full"] }
r2d2 = { version = "0.8.10" }
lazy_static = { version = "1.4.0" }
serde = { version = "1.0.147", features=["derive"] }
serde_json = { version = "1.0.87" }
log = { version = "0.4.17" }
pretty_env_logger = { version = "0.4.0" }

View file

@ -1,60 +1,28 @@
mod config;
mod routes;
use axum::routing::{get, put};
use axum::{Json, Extension};
use redis::AsyncCommands;
use axum::routing::{get, post};
#[tokio::main]
async fn main() {
pretty_env_logger::init();
log::debug!("Logging initialized!");
log::debug!("Opening Redis client...");
let rclient = redis::Client::open(&**config::REDIS_CONN)
.expect("to be able to connect to Redis");
log::debug!("Configuring Axum router...");
let webapp = axum::Router::new()
.route("/", get(get_home))
.route("/leaderboard", get(get_leaderboard))
.route("/score", get(get_score))
.route("/score", put(update_score))
.layer(Extension(rclient));
.route("/", get(routes::home::route_home_get))
.route("/board/", post(routes::board::route_board_post))
.layer(axum::Extension(rclient));
log::info!("Starting Axum server...");
axum::Server::bind(&config::AXUM_HOST).serve(webapp.into_make_service()).await
.expect("to be able to run the Axum server");
}
async fn get_home(
Extension(rclient): Extension<redis::Client>
) -> String {
let mut rconn = rclient.get_async_connection().await
.expect("to be able to create a Redis connection");
rconn.set::<&str, &str, String>("hello", "world").await
.expect("to be able to set things in redis");
let world: String = rconn.get("hello").await
.expect("to be able to get things from redis");
format!("Hello {world}!")
}
async fn get_leaderboard(
Extension(rclient): Extension<redis::Client>
) {
todo!()
}
async fn get_score(
Extension(rclient): Extension<redis::Client>
) {
todo!()
}
async fn update_score(
Extension(rclient): Extension<redis::Client>
) {
todo!()
}

101
src/routes/board.rs Normal file
View file

@ -0,0 +1,101 @@
//! Module defining routes for `/board/`.
use axum::{Extension, Json};
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use redis::AsyncCommands;
use serde_json::json;
use serde::Serialize;
use serde::Deserialize;
/// Possible results for `POST /board/`.
pub enum OutcomeBoardPost {
/// Could not connect to Redis.
RedisConnectionError,
/// Could not check the existence of the board on Redis.
RedisCheckExistenceError,
/// Could not set the board ordering on Redis.
RedisSetOrderError,
/// Board already exists.
AlreadyExists,
/// Board created successfully.
Success,
}
use OutcomeBoardPost::*;
impl IntoResponse for OutcomeBoardPost {
fn into_response(self) -> Response {
let (status, response) = match self {
RedisConnectionError => (StatusCode::GATEWAY_TIMEOUT, json!("Could not connect to Redis")),
RedisCheckExistenceError => (StatusCode::INTERNAL_SERVER_ERROR, json!("Could not check if the board already exists")),
RedisSetOrderError => (StatusCode::INTERNAL_SERVER_ERROR, json!("Could not set the board's ordering")),
AlreadyExists => (StatusCode::CONFLICT, json!("Board already exists")),
Success => (StatusCode::OK, json!([]))
};
IntoResponse::into_response((status, Json(response)))
}
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
pub enum Order {
/// The greater the score, the worse it is.
Ascending,
/// The greater the score, the better it is.
Descending,
}
/// Expected input data for `POST /board/`.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct RouteBoardPostInput {
name: String,
order: Order,
}
/// Handler for `POST /board/`.
///
/// Creates a new board, storing the details on Redis.
///
/// Will refuse to overwrite an already existing board.
pub async fn route_board_post(
Extension(rclient): Extension<redis::Client>,
Json(input): Json<RouteBoardPostInput>,
) -> Result<OutcomeBoardPost, OutcomeBoardPost> {
log::trace!("Connecting to Redis...");
let mut rconn = rclient.get_async_connection().await
.map_err(|_| RedisConnectionError)?;
let name = &input.name;
let order_key = format!("board:{name}:order");
let scores_key = format!("board:{name}:scores");
log::trace!("Checking that the board does not already exist via the order key...");
redis::cmd("TYPE").arg(&order_key).query_async::<redis::aio::Connection, String>(&mut rconn).await
.map_err(|_| RedisCheckExistenceError)?
.eq("none").then_some(())
.ok_or(AlreadyExists)?;
// Possibly superfluous, but better be safe than sorry
log::trace!("Checking that the board does not already exist via the scores key...");
redis::cmd("TYPE").arg(&scores_key).query_async::<redis::aio::Connection, String>(&mut rconn).await
.map_err(|_| RedisCheckExistenceError)?
.eq("none").then_some(())
.ok_or(AlreadyExists)?;
log::info!("Creating board: {}", &name);
log::trace!("Setting the board order...");
rconn.set(&order_key, match input.order {
Order::Ascending => "LT",
Order::Descending => "GT",
}).await
.map_err(|_| RedisSetOrderError)?;
Ok(Success)
}

55
src/routes/home.rs Normal file
View file

@ -0,0 +1,55 @@
//! Module defining routes for `/`.
use axum::{Extension, Json};
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use serde_json::json;
/// Possible results for `GET /`.
pub enum OutcomeHomeGet {
/// Could not connect to Redis.
RedisConnectionError,
/// Could not PING Redis.
RedisPingError,
/// Did not get a PONG back from Redis.
RedisPongError,
/// Ping successful.
Success,
}
use OutcomeHomeGet::*;
impl IntoResponse for OutcomeHomeGet {
fn into_response(self) -> Response {
let (status, response) = match self {
RedisConnectionError => (StatusCode::GATEWAY_TIMEOUT, json!("Could not connect to Redis")),
RedisPingError => (StatusCode::BAD_GATEWAY, json!("Could not ping Redis")),
RedisPongError => (StatusCode::INTERNAL_SERVER_ERROR, json!("Redis did not pong back")),
Success => (StatusCode::OK, json!("Welcome to distributed_arcade! Redis seems to be working correctly."))
};
IntoResponse::into_response((status, Json(response)))
}
}
/// Handler for `GET /`.
///
/// Pings Redis to verify that everything is working correctly.
pub async fn route_home_get(
Extension(rclient): Extension<redis::Client>
) -> Result<OutcomeHomeGet, OutcomeHomeGet> {
log::trace!("Connecting to Redis...");
let mut rconn = rclient.get_async_connection().await
.map_err(|_| RedisConnectionError)?;
log::trace!("Sending PING...");
let pong = redis::cmd("PING").query_async::<redis::aio::Connection, String>(&mut rconn).await
.map_err(|_| RedisPingError)?;
log::trace!("Expecting PONG: {pong:?}");
pong.eq("PONG")
.then_some(Success).ok_or(RedisPongError)
}

2
src/routes/mod.rs Normal file
View file

@ -0,0 +1,2 @@
pub mod home;
pub mod board;