Initial commit
This commit is contained in:
commit
b96420ec1d
|
@ -0,0 +1 @@
|
|||
/target
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,32 @@
|
|||
[package]
|
||||
name = "lan-party"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
rocket = { version = "0.5.0-rc.2", features = ["json"] }
|
||||
dashmap = "5.3.4"
|
||||
thiserror = "1.0"
|
||||
schemars = "0.8.10"
|
||||
okapi = { version = "0.7.0-rc.1" }
|
||||
rocket_okapi = { version = "0.8.0-rc.2", features = ["swagger", "rocket_db_pools"] }
|
||||
futures = "0.3"
|
||||
|
||||
[dependencies.sqlx]
|
||||
version = "*"
|
||||
default-features = false
|
||||
features = ["macros", "offline", "migrate"]
|
||||
|
||||
[dependencies.rocket_db_pools]
|
||||
version = "0.1.0-rc.2"
|
||||
features = ["sqlx_sqlite"]
|
||||
|
||||
[dependencies.uuid]
|
||||
version = "1.1.2"
|
||||
features = [
|
||||
"v4", # Lets you generate random UUIDs
|
||||
"fast-rng", # Use a faster (but still sufficiently random) RNG
|
||||
"macro-diagnostics", # Enable better diagnostics for compile-time UUIDs
|
||||
]
|
|
@ -0,0 +1,2 @@
|
|||
[default.databases.party]
|
||||
url = "party.sqlite"
|
|
@ -0,0 +1,5 @@
|
|||
// generated by `sqlx migrate build-script`
|
||||
fn main() {
|
||||
// trigger recompilation when a new migration is added
|
||||
println!("cargo:rerun-if-changed=db/migrations");
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name VARCHAR NOT NULL,
|
||||
score INTEGER NOT NULL DEFAULT 0
|
||||
);
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,2 @@
|
|||
imports_granularity = "Crate"
|
||||
edition = "2021"
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"db": "SQLite"
|
||||
}
|
|
@ -0,0 +1,246 @@
|
|||
use futures::{prelude::*, stream::TryStreamExt};
|
||||
use rocket::{
|
||||
fairing,
|
||||
fairing::AdHoc,
|
||||
http::Status,
|
||||
response,
|
||||
response::{status::Created, Responder},
|
||||
serde::{json::Json, Deserialize, Serialize},
|
||||
Build, Request, Rocket,
|
||||
};
|
||||
use rocket_db_pools::{
|
||||
sqlx::{self},
|
||||
Connection, Database,
|
||||
};
|
||||
use rocket_okapi::{
|
||||
openapi, openapi_get_routes,
|
||||
response::OpenApiResponderInner,
|
||||
swagger_ui::{make_swagger_ui, SwaggerUIConfig},
|
||||
JsonSchema,
|
||||
};
|
||||
use sqlx::{Acquire, Connection as SqlxConnection, FromRow, Sqlite};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[macro_use]
|
||||
extern crate rocket;
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum PartyError {
|
||||
#[error("user `{0}` does not exist")]
|
||||
UserNotFound(i64),
|
||||
#[error("unknown error: {0}")]
|
||||
Unknown(String),
|
||||
#[error("invalid parameter: {0}")]
|
||||
InvalidParameter(String),
|
||||
#[error("uuid error {source:?}")]
|
||||
UuidError {
|
||||
#[from]
|
||||
source: uuid::Error,
|
||||
},
|
||||
#[error("sqlx error {source:?}")]
|
||||
SqlxError {
|
||||
#[from]
|
||||
source: sqlx::Error,
|
||||
},
|
||||
}
|
||||
|
||||
impl OpenApiResponderInner for PartyError {
|
||||
fn responses(
|
||||
_gen: &mut rocket_okapi::gen::OpenApiGenerator,
|
||||
) -> rocket_okapi::Result<okapi::openapi3::Responses> {
|
||||
Ok(okapi::openapi3::Responses::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r, 'o: 'r> Responder<'r, 'o> for PartyError {
|
||||
fn respond_to(self, req: &'r Request<'_>) -> response::Result<'o> {
|
||||
// log `self` to your favored error tracker, e.g.
|
||||
// sentry::capture_error(&self);
|
||||
|
||||
match self {
|
||||
// in our simplistic example, we're happy to respond with the default 500 responder in all cases
|
||||
Self::UserNotFound(_) => Status::NotFound,
|
||||
_ => Status::InternalServerError,
|
||||
}
|
||||
.respond_to(req)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, FromForm, Serialize, Deserialize, JsonSchema, FromRow)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
struct User {
|
||||
name: String,
|
||||
#[serde(default)]
|
||||
score: i64,
|
||||
#[serde(default)]
|
||||
id: i64,
|
||||
}
|
||||
|
||||
#[derive(Database)]
|
||||
#[database("party")]
|
||||
struct Db(sqlx::SqlitePool);
|
||||
|
||||
#[openapi]
|
||||
#[get("/")]
|
||||
fn index() -> String {
|
||||
format!("Hello, world!")
|
||||
}
|
||||
|
||||
#[openapi]
|
||||
#[post("/user", data = "<user>")]
|
||||
async fn add_user(
|
||||
mut db: Connection<Db>,
|
||||
mut user: Json<User>,
|
||||
) -> Result<Created<Json<User>>, PartyError> {
|
||||
let result = sqlx::query!("INSERT INTO users (name) VALUES (?)", user.name)
|
||||
.execute(&mut *db)
|
||||
.await?;
|
||||
user.id = result.last_insert_rowid();
|
||||
|
||||
Ok(Created::new("/").body(user))
|
||||
}
|
||||
|
||||
#[openapi]
|
||||
#[delete("/user/<id>")]
|
||||
async fn delete_user(mut db: Connection<Db>, id: i64) -> Result<Status, PartyError> {
|
||||
sqlx::query!("DELETE FROM users where (id = ?)", id)
|
||||
.execute(&mut *db)
|
||||
.await?;
|
||||
|
||||
Ok(Status::Ok)
|
||||
}
|
||||
|
||||
#[openapi]
|
||||
#[get("/user/<id>")]
|
||||
async fn get_user(mut db: Connection<Db>, id: i64) -> Result<Json<User>, PartyError> {
|
||||
let user = sqlx::query_as!(User, "SELECT id, name, score FROM users WHERE (id = ?)", id)
|
||||
.fetch_one(&mut *db)
|
||||
.await?;
|
||||
Ok(Json(user))
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, FromFormField, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
enum UserSort {
|
||||
#[field(value = "score")]
|
||||
Score,
|
||||
#[field(value = "name")]
|
||||
Name,
|
||||
#[field(value = "id")]
|
||||
Id,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, FromFormField, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
enum Ordering {
|
||||
#[field(value = "desc")]
|
||||
Desc,
|
||||
#[field(value = "asc")]
|
||||
Asc,
|
||||
}
|
||||
|
||||
impl ToString for Ordering {
|
||||
fn to_string(&self) -> String {
|
||||
match self {
|
||||
Self::Desc => "DESC",
|
||||
Self::Asc => "ASC",
|
||||
}
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl ToString for UserSort {
|
||||
fn to_string(&self) -> String {
|
||||
match self {
|
||||
Self::Score => "score",
|
||||
Self::Name => "name",
|
||||
Self::Id => "id",
|
||||
}
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
#[openapi]
|
||||
#[get("/user?<sort>&<order>")]
|
||||
async fn get_all_users(
|
||||
mut db: Connection<Db>,
|
||||
sort: Option<UserSort>,
|
||||
order: Option<Ordering>,
|
||||
) -> Result<Json<Vec<User>>, PartyError> {
|
||||
let users = sqlx::query_as::<_, User>(&format!(
|
||||
"SELECT id, name, score FROM users ORDER BY {} {}",
|
||||
sort.unwrap_or(UserSort::Id).to_string(),
|
||||
order.unwrap_or(Ordering::Asc).to_string()
|
||||
))
|
||||
.fetch_all(&mut *db)
|
||||
.await?;
|
||||
|
||||
Ok(Json(users))
|
||||
}
|
||||
|
||||
#[openapi]
|
||||
#[post("/user/<id>/score", data = "<score>")]
|
||||
async fn set_score(
|
||||
mut db: Connection<Db>,
|
||||
id: i64,
|
||||
score: Json<i64>,
|
||||
) -> Result<Status, PartyError> {
|
||||
sqlx::query!("UPDATE users SET score = ? WHERE id = ?", *score, id)
|
||||
.execute(&mut *db)
|
||||
.await?;
|
||||
Ok(Status::Ok)
|
||||
}
|
||||
|
||||
#[openapi]
|
||||
#[get("/user/<id>/score")]
|
||||
async fn get_score(mut db: Connection<Db>, id: i64) -> Result<Json<i64>, PartyError> {
|
||||
let score = sqlx::query_scalar!("SELECT score FROM users WHERE id = ?", id)
|
||||
.fetch_one(&mut *db)
|
||||
.await?;
|
||||
Ok(Json(score))
|
||||
}
|
||||
|
||||
fn get_docs() -> SwaggerUIConfig {
|
||||
SwaggerUIConfig {
|
||||
url: "../api/openapi.json".to_owned(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
async fn run_migrations(rocket: Rocket<Build>) -> fairing::Result {
|
||||
match Db::fetch(&rocket) {
|
||||
Some(db) => match sqlx::migrate!("db/migrations").run(&**db).await {
|
||||
Ok(_) => {
|
||||
println!("Migrations completed");
|
||||
Ok(rocket)
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to initialize SQLx database: {}", e);
|
||||
Err(rocket)
|
||||
}
|
||||
},
|
||||
None => Err(rocket),
|
||||
}
|
||||
}
|
||||
|
||||
#[launch]
|
||||
fn rocket() -> _ {
|
||||
rocket::build()
|
||||
.attach(Db::init())
|
||||
.attach(AdHoc::try_on_ignite("SQLx Migrations", run_migrations))
|
||||
.mount("/", openapi_get_routes![index])
|
||||
.mount(
|
||||
"/api",
|
||||
openapi_get_routes![
|
||||
add_user,
|
||||
get_user,
|
||||
get_all_users,
|
||||
delete_user,
|
||||
set_score,
|
||||
get_score
|
||||
],
|
||||
)
|
||||
.mount("/swagger", make_swagger_ui(&get_docs()))
|
||||
}
|
Loading…
Reference in New Issue