polaris-mirror/src/rocket_api.rs

262 lines
6 KiB
Rust

use rocket::http::{Cookie, Cookies, RawStr, Status};
use rocket::request::{self, FromParam, FromRequest, Request};
use rocket::{Outcome, State};
use rocket_contrib::json::Json;
use std::fs::File;
use std::path::PathBuf;
use std::ops::Deref;
use std::sync::Arc;
use config::{self, Config};
use db::DB;
use errors;
use index;
use serve;
use thumbnails;
use user;
use utils;
use vfs::VFSSource;
const CURRENT_MAJOR_VERSION: i32 = 3;
const CURRENT_MINOR_VERSION: i32 = 0;
const SESSION_FIELD_USERNAME: &str = "username";
pub fn get_routes() -> Vec<rocket::Route> {
routes![
version,
initial_setup,
get_settings,
put_settings,
trigger_index,
auth,
browse_root,
browse,
flatten_root,
flatten,
random,
recent,
search_root,
search,
serve,
]
}
struct Auth {
username: String,
}
impl<'a, 'r> FromRequest<'a, 'r> for Auth {
type Error = ();
fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, ()> {
let mut cookies = request.guard::<Cookies>().unwrap();
match cookies.get_private(SESSION_FIELD_USERNAME) {
Some(u) => Outcome::Success(Auth {
username: u.value().to_string(),
}),
_ => Outcome::Failure((Status::Forbidden, ())),
}
// TODO allow auth via authorization header
}
}
struct AdminRights {}
impl<'a, 'r> FromRequest<'a, 'r> for AdminRights {
type Error = ();
fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, ()> {
let db = request.guard::<State<DB>>()?;
match user::count::<DB>(&db) {
Err(_) => return Outcome::Failure((Status::InternalServerError, ())),
Ok(0) => return Outcome::Success(AdminRights {}),
_ => (),
};
let auth = request.guard::<Auth>()?;
match user::is_admin::<DB>(&db, &auth.username) {
Err(_) => Outcome::Failure((Status::InternalServerError, ())),
Ok(true) => Outcome::Success(AdminRights {}),
Ok(false) => Outcome::Failure((Status::Forbidden, ())),
}
}
}
struct VFSPathBuf {
path_buf: PathBuf,
}
impl<'r> FromParam<'r> for VFSPathBuf {
type Error = &'r RawStr;
fn from_param(param: &'r RawStr) -> Result<Self, Self::Error> {
let decoded_path = param.percent_decode_lossy();
Ok(VFSPathBuf{
path_buf: PathBuf::from(decoded_path.into_owned())
})
}
}
impl From<VFSPathBuf> for PathBuf {
fn from(vfs_path_buf: VFSPathBuf) -> Self {
vfs_path_buf.path_buf.clone()
}
}
#[derive(Serialize)]
struct Version {
major: i32,
minor: i32,
}
#[get("/version")]
fn version() -> Json<Version> {
let current_version = Version {
major: CURRENT_MAJOR_VERSION,
minor: CURRENT_MINOR_VERSION,
};
Json(current_version)
}
#[derive(Serialize)]
struct InitialSetup {
has_any_users: bool,
}
#[get("/initial_setup")]
fn initial_setup(db: State<DB>) -> Result<Json<InitialSetup>, errors::Error> {
let initial_setup = InitialSetup {
has_any_users: user::count::<DB>(&db)? > 0,
};
Ok(Json(initial_setup))
}
#[get("/settings")]
fn get_settings(db: State<DB>, _admin_rights: AdminRights) -> Result<Json<Config>, errors::Error> {
let config = config::read::<DB>(&db)?;
Ok(Json(config))
}
#[put("/settings", data = "<config>")]
fn put_settings(
db: State<DB>,
_admin_rights: AdminRights,
config: Json<Config>,
) -> Result<(), errors::Error> {
config::amend::<DB>(&db, &config)?;
Ok(())
}
#[post("/trigger_index")]
fn trigger_index(
command_sender: State<Arc<index::CommandSender>>,
_admin_rights: AdminRights,
) -> Result<(), errors::Error> {
command_sender.trigger_reindex()?;
Ok(())
}
#[derive(Deserialize)]
struct AuthCredentials {
username: String,
password: String,
}
#[derive(Serialize)]
struct AuthOutput {
admin: bool,
}
#[post("/auth", data = "<credentials>")]
fn auth(
db: State<DB>,
credentials: Json<AuthCredentials>,
mut cookies: Cookies,
) -> Result<Json<AuthOutput>, errors::Error> {
user::auth::<DB>(&db, &credentials.username, &credentials.password)?;
cookies.add_private(Cookie::new(
SESSION_FIELD_USERNAME,
credentials.username.clone(),
));
let auth_output = AuthOutput {
admin: user::is_admin::<DB>(&db, &credentials.username)?,
};
Ok(Json(auth_output))
}
#[get("/browse")]
fn browse_root(
db: State<DB>,
_auth: Auth,
) -> Result<Json<Vec<index::CollectionFile>>, errors::Error> {
let result = index::browse(db.deref(), &PathBuf::new())?;
Ok(Json(result))
}
#[get("/browse/<path>")]
fn browse(
db: State<DB>,
_auth: Auth,
path: VFSPathBuf,
) -> Result<Json<Vec<index::CollectionFile>>, errors::Error> {
let result = index::browse(db.deref(), &path.into() as &PathBuf)?;
Ok(Json(result))
}
#[get("/flatten")]
fn flatten_root(db: State<DB>, _auth: Auth) -> Result<Json<Vec<index::Song>>, errors::Error> {
let result = index::flatten(db.deref(), &PathBuf::new())?;
Ok(Json(result))
}
#[get("/flatten/<path>")]
fn flatten(
db: State<DB>,
_auth: Auth,
path: VFSPathBuf,
) -> Result<Json<Vec<index::Song>>, errors::Error> {
let result = index::flatten(db.deref(), &path.into() as &PathBuf)?;
Ok(Json(result))
}
#[get("/random")]
fn random(db: State<DB>, _auth: Auth) -> Result<Json<Vec<index::Directory>>, errors::Error> {
let result = index::get_random_albums(db.deref(), 20)?;
Ok(Json(result))
}
#[get("/recent")]
fn recent(db: State<DB>, _auth: Auth) -> Result<Json<Vec<index::Directory>>, errors::Error> {
let result = index::get_recent_albums(db.deref(), 20)?;
Ok(Json(result))
}
#[get("/search")]
fn search_root(db: State<DB>, _auth: Auth) -> Result<Json<Vec<index::CollectionFile>>, errors::Error> {
let result = index::search(db.deref(), "")?;
Ok(Json(result))
}
#[get("/search/<query>")]
fn search(db: State<DB>, _auth: Auth, query: String) -> Result<Json<Vec<index::CollectionFile>>, errors::Error> {
let result = index::search(db.deref(), &query)?;
Ok(Json(result))
}
#[get("/serve/<path>")]
fn serve(db: State<DB>, _auth: Auth, path: VFSPathBuf) -> Result<serve::RangeResponder<File>, errors::Error> {
let db: &DB = db.deref();
let vfs = db.get_vfs()?;
let real_path = vfs.virtual_to_real(&path.into() as &PathBuf)?;
let serve_path = if utils::is_image(&real_path) {
thumbnails::get_thumbnail(&real_path, 400)?
} else {
real_path
};
let file = File::open(serve_path)?;
Ok(serve::RangeResponder::new(file))
}