2022-08-29 13:53:39 +02:00
|
|
|
mod api;
|
|
|
|
|
|
|
|
use lazy_static::lazy_static;
|
|
|
|
use okapi::openapi3::{Object, Responses, SecurityRequirement, SecurityScheme, SecuritySchemeData};
|
2022-08-29 12:03:21 +02:00
|
|
|
use rocket::{
|
2022-08-29 13:53:39 +02:00
|
|
|
fairing, fairing::AdHoc, http::Status, request, request::FromRequest, response,
|
|
|
|
response::Responder, serde, Build, Request, Rocket,
|
2022-08-29 12:03:21 +02:00
|
|
|
};
|
2022-08-29 13:53:39 +02:00
|
|
|
use rocket_db_pools::{sqlx, Database};
|
2022-08-29 12:03:21 +02:00
|
|
|
use rocket_okapi::{
|
2022-08-29 13:53:39 +02:00
|
|
|
gen::OpenApiGenerator,
|
|
|
|
mount_endpoints_and_merged_docs, openapi, openapi_get_routes,
|
|
|
|
rapidoc::*,
|
|
|
|
request::{OpenApiFromRequest, RequestHeaderInput},
|
2022-08-29 12:03:21 +02:00
|
|
|
response::OpenApiResponderInner,
|
2022-08-29 13:53:39 +02:00
|
|
|
settings::{OpenApiSettings, UrlObject},
|
2022-08-29 12:03:21 +02:00
|
|
|
swagger_ui::{make_swagger_ui, SwaggerUIConfig},
|
|
|
|
};
|
|
|
|
|
|
|
|
#[macro_use]
|
|
|
|
extern crate rocket;
|
|
|
|
|
2022-08-29 13:53:39 +02:00
|
|
|
use schemars::JsonSchema;
|
2022-08-29 12:03:21 +02:00
|
|
|
use thiserror::Error;
|
|
|
|
|
2022-08-29 13:53:39 +02:00
|
|
|
/*
|
|
|
|
const API_KEYS: [&'static str; 3] = [
|
|
|
|
"7de10bf6-278d-11ed-ad60-a8a15919d1b3",
|
|
|
|
"89eb06e0-278d-11ed-9b29-a8a15919d1b3",
|
|
|
|
"8a35ba14-278d-11ed-a200-a8a15919d1b3",
|
|
|
|
];
|
|
|
|
*/
|
|
|
|
|
|
|
|
lazy_static! {
|
|
|
|
static ref API_KEYS: Vec<String> = {
|
|
|
|
serde_json::from_str(
|
|
|
|
&std::fs::read_to_string("api_keys.json").expect("api_keys.json does not exist"),
|
|
|
|
)
|
|
|
|
.expect("api_keys.json is not valid")
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2022-08-29 12:03:21 +02:00
|
|
|
#[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> {
|
|
|
|
match self {
|
|
|
|
Self::UserNotFound(_) => Status::NotFound,
|
|
|
|
_ => Status::InternalServerError,
|
|
|
|
}
|
|
|
|
.respond_to(req)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-29 13:53:39 +02:00
|
|
|
/// # Ordering
|
|
|
|
///
|
|
|
|
/// Ordering of data in an array, ascending or descending
|
2022-08-29 12:03:21 +02:00
|
|
|
#[derive(Clone, Debug, FromFormField, JsonSchema)]
|
|
|
|
#[serde(rename_all = "snake_case")]
|
2022-08-29 13:53:39 +02:00
|
|
|
pub enum Ordering {
|
2022-08-29 12:03:21 +02:00
|
|
|
#[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()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-29 13:53:39 +02:00
|
|
|
#[derive(Database)]
|
|
|
|
#[database("party")]
|
|
|
|
pub struct Db(sqlx::SqlitePool);
|
|
|
|
|
|
|
|
#[derive(Clone, Copy, Debug)]
|
|
|
|
pub struct ApiKey;
|
|
|
|
|
|
|
|
#[rocket::async_trait]
|
|
|
|
impl<'r> FromRequest<'r> for ApiKey {
|
|
|
|
type Error = ApiKey;
|
|
|
|
|
|
|
|
async fn from_request(req: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
|
|
|
|
if req
|
|
|
|
.headers()
|
|
|
|
.get("X-API-Key")
|
|
|
|
.any(|k| API_KEYS.contains(&String::from(k)))
|
|
|
|
{
|
|
|
|
request::Outcome::Success(ApiKey)
|
|
|
|
} else {
|
|
|
|
request::Outcome::Failure((Status::Unauthorized, ApiKey))
|
2022-08-29 12:03:21 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-29 13:53:39 +02:00
|
|
|
impl<'a> OpenApiFromRequest<'a> for ApiKey {
|
|
|
|
fn from_request_input(
|
|
|
|
_gen: &mut OpenApiGenerator,
|
|
|
|
_name: String,
|
|
|
|
_required: bool,
|
|
|
|
) -> rocket_okapi::Result<RequestHeaderInput> {
|
|
|
|
// Setup global requirement for Security scheme
|
|
|
|
let security_scheme = SecurityScheme {
|
|
|
|
description: Some("Requires an API key to access".to_owned()),
|
|
|
|
// Setup data requirements.
|
|
|
|
// This can be part of the `header`, `query` or `cookie`.
|
|
|
|
// In this case the header `x-api-key: mykey` needs to be set.
|
|
|
|
data: SecuritySchemeData::ApiKey {
|
|
|
|
name: "x-api-key".to_owned(),
|
|
|
|
location: "header".to_owned(),
|
|
|
|
},
|
|
|
|
extensions: Object::default(),
|
|
|
|
};
|
|
|
|
// Add the requirement for this route/endpoint
|
|
|
|
// This can change between routes.
|
|
|
|
let mut security_req = SecurityRequirement::new();
|
|
|
|
// Each security requirement needs to be met before access is allowed.
|
|
|
|
security_req.insert("ApiKeyAuth".to_owned(), Vec::new());
|
|
|
|
// These vvvvvvv-----^^^^^^^^^^ values need to match exactly!
|
|
|
|
Ok(RequestHeaderInput::Security(
|
|
|
|
"ApiKeyAuth".to_owned(),
|
|
|
|
security_scheme,
|
|
|
|
security_req,
|
|
|
|
))
|
|
|
|
}
|
2022-08-29 12:03:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#[openapi]
|
2022-08-29 13:53:39 +02:00
|
|
|
#[get("/")]
|
|
|
|
fn index() -> String {
|
|
|
|
format!("Hello, world!")
|
2022-08-29 12:03:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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() -> _ {
|
2022-08-29 13:53:39 +02:00
|
|
|
println!("{:#?}", API_KEYS.len());
|
|
|
|
let mut building_rocket = rocket::build()
|
2022-08-29 12:03:21 +02:00
|
|
|
.attach(Db::init())
|
|
|
|
.attach(AdHoc::try_on_ignite("SQLx Migrations", run_migrations))
|
2022-08-29 13:53:39 +02:00
|
|
|
.mount("/", routes![index])
|
2022-08-29 12:03:21 +02:00
|
|
|
.mount("/swagger", make_swagger_ui(&get_docs()))
|
2022-08-29 13:53:39 +02:00
|
|
|
.mount(
|
|
|
|
"/rapidoc/",
|
|
|
|
make_rapidoc(&RapiDocConfig {
|
|
|
|
general: GeneralConfig {
|
|
|
|
spec_urls: vec![UrlObject::new("General", "../api/openapi.json")],
|
|
|
|
..Default::default()
|
|
|
|
},
|
|
|
|
hide_show: HideShowConfig {
|
|
|
|
allow_spec_url_load: false,
|
|
|
|
allow_spec_file_load: false,
|
|
|
|
..Default::default()
|
|
|
|
},
|
|
|
|
..Default::default()
|
|
|
|
}),
|
|
|
|
);
|
|
|
|
|
|
|
|
let openapi_settings = OpenApiSettings::default();
|
|
|
|
mount_endpoints_and_merged_docs! {
|
|
|
|
building_rocket, "/api".to_owned(), openapi_settings,
|
|
|
|
"/user" => api::user::get_routes_and_docs(&openapi_settings),
|
|
|
|
};
|
|
|
|
|
|
|
|
building_rocket
|
2022-08-29 12:03:21 +02:00
|
|
|
}
|