#![forbid(unsafe_code)]
#![deny(clippy::all)]
#![allow(clippy::field_reassign_with_default)]
#![feature(proc_macro_hygiene, decl_macro)]
#![doc(html_root_url = "https://docs.dfstoryteller.com/rust-docs/")]
#![doc(html_favicon_url = "https://docs.dfstoryteller.com/favicon/favicon-16x16.png")]
#![doc(html_logo_url = "https://docs.dfstoryteller.com/logo.svg")]
pub mod api;
pub mod api_errors;
pub mod api_objects;
pub mod graphql;
pub mod item_count;
mod launch_errors;
pub mod pagination;
pub mod server_config;
use anyhow::Error;
use df_st_core::config::RootConfig;
use graphql::{MutationRoot, QueryRoot, Schema};
use launch_errors::handle_launch_errors;
#[allow(unused_imports)]
use log::{debug, error, info, trace, warn};
use rocket::http::{ContentType, Status};
use rocket::*;
use rocket_contrib::serve::StaticFiles;
use rocket_contrib::*;
use rust_embed::RustEmbed;
use serde::Serialize;
use server_config::{set_server_config, ServerInfo};
use std::ffi::OsStr;
use std::fs::File;
use std::io::{Cursor, Write};
use std::path::{Path, PathBuf};
pub use api::get_openapi_spec;
#[derive(RustEmbed)]
#[folder = "./pages/"]
struct StaticAssets;
#[derive(RustEmbed)]
#[folder = "./docs/"]
struct StaticDocs;
#[derive(RustEmbed)]
#[folder = "./serve-paintings/"]
struct StaticPaintings;
#[database("df_st_database")]
pub struct DfStDatabase(df_st_db::DbConnection);
#[get("/")]
fn index_page<'r>() -> response::Result<'r> {
StaticAssets::get("index.html").map_or_else(
|| Err(Status::NotFound),
|d| {
response::Response::build()
.header(ContentType::HTML)
.sized_body(Cursor::new(d))
.ok()
},
)
}
#[get("/static/<file..>")]
fn static_file<'r>(file: PathBuf) -> response::Result<'r> {
let filename = file
.display()
.to_string()
.replace("\\", "/");
StaticAssets::get(&filename).map_or_else(
|| Err(Status::NotFound),
|d| {
let ext = file
.as_path()
.extension()
.and_then(OsStr::to_str)
.ok_or_else(|| Status::new(400, "Could not get file extension"))?;
let content_type = match ext {
"map" => ContentType::JavaScript,
_ => ContentType::from_extension(ext)
.ok_or_else(|| Status::new(400, "Could not get file content type"))?,
};
response::Response::build()
.header(content_type)
.sized_body(Cursor::new(d))
.ok()
},
)
}
#[get("/docs/<file..>")]
fn static_docs<'r>(file: PathBuf) -> response::Result<'r> {
let temp_filename = file
.display()
.to_string()
.replace("\\", "/");
let filename = match temp_filename.as_ref() {
"swagger-ui" => "swagger-ui/index.html".to_string(),
"rapidoc" => "rapidoc/index.html".to_string(),
_ => temp_filename,
};
let file = PathBuf::from(&filename);
StaticDocs::get(&filename).map_or_else(
|| Err(Status::NotFound),
|d| {
let ext = file
.as_path()
.extension()
.and_then(OsStr::to_str)
.ok_or_else(|| Status::new(400, "Could not get file extension"))?;
let content_type = match ext {
"map" => ContentType::JavaScript,
_ => ContentType::from_extension(ext)
.ok_or_else(|| Status::new(400, "Could not get file content type"))?,
};
response::Response::build()
.header(content_type)
.sized_body(Cursor::new(d))
.ok()
},
)
}
pub fn write_json_file<P: AsRef<std::path::Path>, C: Serialize>(
filename: P,
object: &C,
) -> Result<(), Error> {
let mut file = File::create(filename)?;
let json_string = serde_json::to_string_pretty(object)?;
file.write_all(json_string.as_bytes())?;
Ok(())
}
pub fn create_serve_paintings_folder() -> Result<(), Error> {
let folder_name = "./serve-paintings";
if !Path::new(folder_name).exists() {
info!(
"Creating `serve-paintings` folder in: {}",
std::env::current_dir()?
.to_str()
.unwrap_or("path contains special characters it could not display.")
);
std::fs::create_dir(folder_name)?;
std::fs::create_dir(format!("{}/timeline", folder_name))?;
std::fs::create_dir(format!("{}/timeline/chartjs/", folder_name))?;
let file_list_copy = vec![
"index.html",
"paintings.json",
"timeline/index.html",
"timeline/chartjs/Chart.min.css",
"timeline/chartjs/Chart.min.js",
];
for filename in file_list_copy {
let file_bytes = match StaticPaintings::get(filename) {
Some(data) => data,
None => {
panic!("Could not get file: {}", filename);
}
};
let mut file = File::create(format!("{}/{}", folder_name, filename))?;
file.write_all(&file_bytes)?;
}
}
Ok(())
}
pub fn create_service(server_config: &RootConfig, world_id: u32) -> rocket::Rocket {
let cors = rocket_cors::CorsOptions::default().to_cors().unwrap();
let server_info = ServerInfo::new(&server_config, "/api/", world_id);
info!("Your `World_id` = `{}`", server_info.world_id);
info!("Your `Base_url` = `{}`", server_info.base_url);
rocket::custom(set_server_config(&server_config))
.attach(DfStDatabase::fairing())
.attach(cors)
.register(catchers![
api_errors::internal_error,
api_errors::not_found,
api_errors::no_content,
api_errors::not_modified
])
.manage(server_info)
.mount("/", routes![index_page, static_file, static_docs])
.mount("/paintings", StaticFiles::from("./serve-paintings"))
.mount("/api/", api::get_routes())
.manage(Schema::new(QueryRoot, MutationRoot))
.mount(
"/graphql/",
rocket::routes![
graphql::graphiql,
graphql::get_graphql_handler,
graphql::post_graphql_handler,
graphql::graphiql_playground,
],
)
}
pub fn start_server(server_config: &RootConfig, world_id: u32) {
info!("Starting server");
match create_serve_paintings_folder() {
Ok(_) => {}
Err(err) => {
error!("{}", err);
}
}
let launch_error = create_service(server_config, world_id).launch();
handle_launch_errors(launch_error);
}