parent
4e98df4498
commit
1dfb3d4964
21 changed files with 338 additions and 523 deletions
121
src/cache.rs
121
src/cache.rs
|
@ -1,121 +0,0 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use rocket::tokio::sync::RwLock;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! use_api_cache {
|
||||
($route:literal, $id:ident, $cache:ident) => {
|
||||
if let Some(ret) = $cache.get_only($route, $id).await {
|
||||
return Ok(serde_json::from_str(&ret).unwrap());
|
||||
}
|
||||
};
|
||||
($route:literal, $id:literal, $cache:ident) => {
|
||||
if let Some(ret) = $cache.get_only($route, $id).await {
|
||||
return Ok(serde_json::from_str(&ret).unwrap());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub struct RouteCache {
|
||||
inner: RwLock<HashMap<String, HashMap<String, Option<String>>>>,
|
||||
}
|
||||
|
||||
impl RouteCache {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
inner: RwLock::new(HashMap::new()),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get<F, Fut>(&self, route: &str, id: &str, generator: F) -> String
|
||||
where
|
||||
F: FnOnce() -> Fut,
|
||||
Fut: std::future::Future<Output = String>,
|
||||
{
|
||||
{
|
||||
// Try to get a read lock first.
|
||||
let lock = self.inner.read().await;
|
||||
if let Some(inner_map) = lock.get(route) {
|
||||
if let Some(cached_value) = inner_map.get(id) {
|
||||
log::info!("Using cached value for {route} / {id}");
|
||||
return cached_value.clone().unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the value was not found, acquire a write lock to insert the computed value.
|
||||
let mut lock = self.inner.write().await;
|
||||
|
||||
log::info!("Computing value for {route} / {id}");
|
||||
let computed = generator().await;
|
||||
|
||||
lock.entry(route.to_string())
|
||||
.or_insert_with(HashMap::new)
|
||||
.insert(id.to_string(), Some(computed.clone()));
|
||||
|
||||
computed
|
||||
}
|
||||
|
||||
pub async fn get_only(&self, route: &str, id: &str) -> Option<String> {
|
||||
let lock = self.inner.read().await;
|
||||
if let Some(inner_map) = lock.get(route) {
|
||||
if let Some(cached_value) = inner_map.get(id) {
|
||||
log::info!("Using cached value for {route} / {id}");
|
||||
return cached_value.clone();
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub async fn get_option<F, Fut>(&self, route: &str, id: &str, generator: F) -> Option<String>
|
||||
where
|
||||
F: FnOnce() -> Fut,
|
||||
Fut: std::future::Future<Output = Option<String>>,
|
||||
{
|
||||
{
|
||||
// Try to get a read lock first.
|
||||
let lock = self.inner.read().await;
|
||||
if let Some(inner_map) = lock.get(route) {
|
||||
if let Some(cached_value) = inner_map.get(id) {
|
||||
log::info!("Using cached value for {route} / {id}");
|
||||
return cached_value.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the value was not found, acquire a write lock to insert the computed value.
|
||||
let mut lock = self.inner.write().await;
|
||||
|
||||
log::info!("Computing value for {route} / {id}");
|
||||
let computed = generator().await;
|
||||
|
||||
lock.entry(route.to_string())
|
||||
.or_insert_with(HashMap::new)
|
||||
.insert(id.to_string(), computed.clone());
|
||||
|
||||
computed
|
||||
}
|
||||
|
||||
pub async fn insert(&self, route: &str, id: &str, value: String) {
|
||||
let mut lock = self.inner.write().await;
|
||||
|
||||
log::info!("Inserting value for {route} / {id}");
|
||||
|
||||
lock.entry(route.to_string())
|
||||
.or_insert_with(HashMap::new)
|
||||
.insert(id.to_string(), Some(value));
|
||||
}
|
||||
|
||||
pub async fn invalidate(&self, route: &str, id: &str) {
|
||||
let mut lock = self.inner.write().await;
|
||||
if let Some(inner_map) = lock.get_mut(route) {
|
||||
inner_map.remove(id);
|
||||
|
||||
// If the inner map is empty, remove the route entry as well.
|
||||
if inner_map.is_empty() {
|
||||
lock.remove(route);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
use crate::route::ToAPI;
|
||||
use based::request::api::ToAPI;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
use sqlx::FromRow;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::route::ToAPI;
|
||||
use based::request::api::ToAPI;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
use sqlx::FromRow;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use based::{auth::User, get_pg};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::prelude::FromRow;
|
||||
|
||||
use crate::{get_pg, library::user::User};
|
||||
/// Represents a user event in the database.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
|
||||
pub struct Event {
|
||||
|
|
|
@ -1,16 +1,13 @@
|
|||
use album::Album;
|
||||
use artist::Artist;
|
||||
use based::{get_pg, request::cache::RouteCache};
|
||||
use serde_json::json;
|
||||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use album::Album;
|
||||
use artist::Artist;
|
||||
use serde_json::json;
|
||||
use track::Track;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
use crate::{cache::RouteCache, get_pg};
|
||||
|
||||
pub mod album;
|
||||
pub mod artist;
|
||||
pub mod event;
|
||||
|
@ -18,7 +15,6 @@ pub mod metadata;
|
|||
pub mod playlist;
|
||||
pub mod search;
|
||||
pub mod track;
|
||||
pub mod user;
|
||||
|
||||
/// Checks if a file has a music file extension
|
||||
fn is_music_file(path: &Path) -> bool {
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
use based::{auth::User, get_pg};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::FromRow;
|
||||
|
||||
use crate::{get_pg, library::user::User};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
|
||||
pub struct Playlist {
|
||||
// Unique identifier of the playlist
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
use super::{album::Album, artist::Artist, playlist::Playlist, track::Track};
|
||||
use based::request::api::ToAPI;
|
||||
use serde::Serialize;
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use crate::route::ToAPI;
|
||||
|
||||
use super::{album::Album, artist::Artist, playlist::Playlist, track::Track};
|
||||
|
||||
// Calculate a score for a given field based on its similarity to the search term and recency factor
|
||||
fn calculate_score(field: &str, search_term: &str, date_added: Option<i64>) -> f64 {
|
||||
// Exact match bonus: assign a high score if the field exactly matches the search term
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
use crate::library::album::Album;
|
||||
use based::{
|
||||
auth::User,
|
||||
get_pg,
|
||||
request::api::{to_uuid, ToAPI},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
use sqlx::prelude::FromRow;
|
||||
use std::{io::Read, str::FromStr};
|
||||
|
||||
use crate::{
|
||||
get_pg,
|
||||
library::album::Album,
|
||||
route::{to_uuid, ToAPI},
|
||||
};
|
||||
|
||||
use super::{metadata::AudioMetadata, user::User};
|
||||
use super::metadata::AudioMetadata;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
|
||||
pub struct Track {
|
||||
|
|
|
@ -1,154 +0,0 @@
|
|||
use crate::route::ToAPI;
|
||||
use data_encoding::HEXUPPER;
|
||||
use rand::RngCore;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
use sqlx::FromRow;
|
||||
|
||||
use crate::get_pg;
|
||||
|
||||
fn gen_token(token_length: usize) -> String {
|
||||
let mut token_bytes = vec![0u8; token_length];
|
||||
|
||||
rand::thread_rng().fill_bytes(&mut token_bytes);
|
||||
|
||||
HEXUPPER.encode(&token_bytes)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
|
||||
pub struct User {
|
||||
/// The username chosen by the user
|
||||
pub username: String,
|
||||
/// The hashed password for the user
|
||||
pub password: String,
|
||||
/// The role of the user
|
||||
pub user_role: UserRole,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, sqlx::Type)]
|
||||
#[sqlx(type_name = "user_role", rename_all = "lowercase")]
|
||||
pub enum UserRole {
|
||||
/// A regular user with limited permissions
|
||||
Regular,
|
||||
/// An admin user with full system privileges
|
||||
Admin,
|
||||
}
|
||||
|
||||
impl User {
|
||||
/// Find a user by their username
|
||||
pub async fn find(username: &str) -> Option<Self> {
|
||||
sqlx::query_as("SELECT * FROM users WHERE username = $1")
|
||||
.bind(username)
|
||||
.fetch_optional(get_pg!())
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// Create a new user with the given details
|
||||
///
|
||||
/// Returns an Option containing the created user, or None if a user already exists with the same username
|
||||
pub async fn create(username: &str, password: &str, role: UserRole) -> Option<Self> {
|
||||
// Check if a user already exists with the same username
|
||||
if Self::find(username).await.is_some() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let u = Self {
|
||||
username: username.to_string(),
|
||||
password: bcrypt::hash(password, bcrypt::DEFAULT_COST).unwrap(),
|
||||
user_role: role,
|
||||
};
|
||||
|
||||
sqlx::query("INSERT INTO users (username, \"password\", user_role) VALUES ($1, $2, $3)")
|
||||
.bind(&u.username)
|
||||
.bind(&u.password)
|
||||
.bind(&u.user_role)
|
||||
.execute(get_pg!())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
Some(u)
|
||||
}
|
||||
|
||||
/// Login a user with the given username and password
|
||||
pub async fn login(username: &str, password: &str) -> Option<(Session, UserRole)> {
|
||||
let u = Self::find(username).await?;
|
||||
|
||||
if !u.verify_pw(password) {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some((u.session().await, u.user_role))
|
||||
}
|
||||
|
||||
/// Change the password of a User
|
||||
///
|
||||
/// Returns a Result indicating whether the password change was successful or not
|
||||
pub async fn passwd(self, old: &str, new: &str) -> Result<(), ()> {
|
||||
if self.verify_pw(old) {
|
||||
sqlx::query("UPDATE users SET \"password\" = $1 WHERE username = $2;")
|
||||
.bind(bcrypt::hash(new, bcrypt::DEFAULT_COST).unwrap())
|
||||
.bind(&self.username)
|
||||
.fetch_one(get_pg!())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Err(())
|
||||
}
|
||||
|
||||
/// Find all users in the system
|
||||
pub async fn find_all() -> Vec<Self> {
|
||||
sqlx::query_as("SELECT * FROM users")
|
||||
.fetch_all(get_pg!())
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// Generate a new session token for the user
|
||||
///
|
||||
/// Returns a Session instance containing the generated token and associated user
|
||||
pub async fn session(&self) -> Session {
|
||||
sqlx::query_as(
|
||||
"INSERT INTO user_session (token, \"user\") VALUES ($1, $2) RETURNING id, token, \"user\"",
|
||||
)
|
||||
.bind(gen_token(64))
|
||||
.bind(&self.username)
|
||||
.fetch_one(get_pg!())
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// Check if the user is an admin
|
||||
pub const fn is_admin(&self) -> bool {
|
||||
matches!(self.user_role, UserRole::Admin)
|
||||
}
|
||||
|
||||
/// Verify that a provided password matches the hashed password for the user
|
||||
///
|
||||
/// Returns a boolean indicating whether the passwords match or not
|
||||
pub fn verify_pw(&self, password: &str) -> bool {
|
||||
bcrypt::verify(password, &self.password).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl ToAPI for User {
|
||||
async fn api(&self) -> serde_json::Value {
|
||||
json!({
|
||||
"username": self.username,
|
||||
"role": self.user_role
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
|
||||
pub struct Session {
|
||||
/// The unique ID of the session token
|
||||
pub id: uuid::Uuid,
|
||||
/// The generated session token
|
||||
pub token: String,
|
||||
/// The username associated with the session token
|
||||
pub user: String,
|
||||
}
|
29
src/main.rs
29
src/main.rs
|
@ -1,32 +1,13 @@
|
|||
use based::auth::{User, UserRole};
|
||||
use based::get_pg;
|
||||
use based::request::cache;
|
||||
use library::Libary;
|
||||
|
||||
mod cache;
|
||||
mod library;
|
||||
mod route;
|
||||
|
||||
use library::user::{User, UserRole};
|
||||
use rocket::routes;
|
||||
use rocket::tokio::sync::OnceCell;
|
||||
use rocket::{http::Method, launch};
|
||||
|
||||
pub static PG: OnceCell<sqlx::PgPool> = OnceCell::const_new();
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! get_pg {
|
||||
() => {
|
||||
if let Some(client) = $crate::PG.get() {
|
||||
client
|
||||
} else {
|
||||
let client = sqlx::postgres::PgPoolOptions::new()
|
||||
.max_connections(5)
|
||||
.connect(&std::env::var("DATABASE_URL").unwrap())
|
||||
.await
|
||||
.unwrap();
|
||||
$crate::PG.set(client).unwrap();
|
||||
$crate::PG.get().unwrap()
|
||||
}
|
||||
};
|
||||
}
|
||||
mod library;
|
||||
mod route;
|
||||
|
||||
#[launch]
|
||||
async fn rocket() -> _ {
|
||||
|
|
|
@ -1,15 +1,11 @@
|
|||
use super::api_error;
|
||||
use super::FallibleApiResponse;
|
||||
use crate::get_pg;
|
||||
use crate::route::vec_to_api;
|
||||
use crate::library::track::Track;
|
||||
use crate::library::Libary;
|
||||
use based::auth::User;
|
||||
use based::request::api::{vec_to_api, FallibleApiResponse};
|
||||
use based::{check_admin, get_pg};
|
||||
use rocket::{get, State};
|
||||
use serde_json::json;
|
||||
|
||||
use crate::check_admin;
|
||||
use crate::library::track::Track;
|
||||
use crate::library::user::User;
|
||||
use crate::library::Libary;
|
||||
|
||||
#[get("/library/clean")]
|
||||
pub async fn clean_library(lib: &State<Libary>, u: User) -> FallibleApiResponse {
|
||||
check_admin!(u);
|
||||
|
|
|
@ -1,19 +1,13 @@
|
|||
use std::cmp::Ordering;
|
||||
|
||||
use super::api_error;
|
||||
use super::to_uuid;
|
||||
use super::FallibleApiResponse;
|
||||
use rocket::fs::NamedFile;
|
||||
use rocket::{get, State};
|
||||
use serde_json::json;
|
||||
|
||||
use crate::cache::RouteCache;
|
||||
use crate::library::album::Album;
|
||||
use crate::library::track::Track;
|
||||
use crate::library::Libary;
|
||||
use crate::route::vec_to_api;
|
||||
use crate::route::ToAPI;
|
||||
use crate::use_api_cache;
|
||||
use based::request::api::{api_error, to_uuid, vec_to_api, FallibleApiResponse, ToAPI};
|
||||
use based::use_api_cache;
|
||||
use rocket::fs::NamedFile;
|
||||
use rocket::{get, State};
|
||||
use serde_json::json;
|
||||
use std::cmp::Ordering;
|
||||
|
||||
#[get("/artist/<artist_id>/albums")]
|
||||
pub async fn albums_route(artist_id: &str, lib: &State<Libary>) -> FallibleApiResponse {
|
||||
|
|
|
@ -1,8 +1,4 @@
|
|||
use super::api_error;
|
||||
use super::to_uuid;
|
||||
use super::vec_to_api;
|
||||
use super::FallibleApiResponse;
|
||||
use super::ToAPI;
|
||||
use based::request::api::{api_error, to_uuid, vec_to_api, FallibleApiResponse, ToAPI};
|
||||
use fs::NamedFile;
|
||||
use rocket::{fs, get, State};
|
||||
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
use super::api_error;
|
||||
use super::to_uuid;
|
||||
use super::FallibleApiResponse;
|
||||
use crate::library::event::Event;
|
||||
use crate::library::event::EventKind;
|
||||
use crate::library::track::Track;
|
||||
use based::auth::User;
|
||||
use based::request::api::api_error;
|
||||
use based::request::api::to_uuid;
|
||||
use based::request::api::FallibleApiResponse;
|
||||
use rocket::post;
|
||||
use rocket::serde::json::Json;
|
||||
use serde::Deserialize;
|
||||
use serde_json::json;
|
||||
|
||||
use crate::library::event::Event;
|
||||
use crate::library::event::EventKind;
|
||||
use crate::library::track::Track;
|
||||
use crate::library::user::User;
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct EventJson {
|
||||
pub kind: EventKind,
|
||||
|
|
|
@ -16,42 +16,6 @@ pub mod search;
|
|||
pub mod track;
|
||||
pub mod user;
|
||||
|
||||
// todo : rework api
|
||||
|
||||
/// A trait to generate a Model API representation in JSON format.
|
||||
pub trait ToAPI: Sized {
|
||||
/// Generate public API JSON
|
||||
fn api(&self) -> impl std::future::Future<Output = serde_json::Value>;
|
||||
}
|
||||
|
||||
/// Converts a slice of items implementing the `ToAPI` trait into a `Vec` of JSON values.
|
||||
pub async fn vec_to_api(items: &[impl ToAPI]) -> Vec<serde_json::Value> {
|
||||
let mut ret = Vec::with_capacity(items.len());
|
||||
|
||||
for e in items {
|
||||
ret.push(e.api().await);
|
||||
}
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
pub fn to_uuid(id: &str) -> Result<uuid::Uuid, ApiError> {
|
||||
uuid::Uuid::from_str(id).map_err(|_| no_uuid_error())
|
||||
}
|
||||
|
||||
type ApiError = BadRequest<serde_json::Value>;
|
||||
type FallibleApiResponse = Result<serde_json::Value, ApiError>;
|
||||
|
||||
pub fn no_uuid_error() -> ApiError {
|
||||
api_error("No valid UUID")
|
||||
}
|
||||
|
||||
pub fn api_error(msg: &str) -> ApiError {
|
||||
BadRequest(json!({
|
||||
"error": msg
|
||||
}))
|
||||
}
|
||||
|
||||
#[get("/")]
|
||||
pub fn index_redir() -> Redirect {
|
||||
Redirect::to(uri!("/web"))
|
||||
|
|
|
@ -1,19 +1,17 @@
|
|||
use crate::get_pg;
|
||||
use crate::library::playlist::Playlist;
|
||||
use crate::library::playlist::Visibility;
|
||||
use crate::library::track::Track;
|
||||
use crate::library::user::User;
|
||||
use based::auth::User;
|
||||
use based::request::api::api_error;
|
||||
use based::request::api::to_uuid;
|
||||
use based::request::api::vec_to_api;
|
||||
use based::request::api::FallibleApiResponse;
|
||||
use rocket::get;
|
||||
use rocket::post;
|
||||
use rocket::serde::json::Json;
|
||||
use serde_json::json;
|
||||
|
||||
use crate::library::playlist::Playlist;
|
||||
use crate::library::playlist::Visibility;
|
||||
use crate::route::FallibleApiResponse;
|
||||
|
||||
use super::api_error;
|
||||
use super::to_uuid;
|
||||
use super::vec_to_api;
|
||||
|
||||
#[get("/playlists")]
|
||||
pub async fn playlists_route(u: User) -> FallibleApiResponse {
|
||||
let mut playlists = vec![
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
use super::api_error;
|
||||
use super::FallibleApiResponse;
|
||||
use based::request::api::{api_error, FallibleApiResponse};
|
||||
use rocket::get;
|
||||
use serde_json::json;
|
||||
|
||||
|
|
|
@ -1,18 +1,13 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
use super::api_error;
|
||||
use super::no_uuid_error;
|
||||
use super::to_uuid;
|
||||
use super::FallibleApiResponse;
|
||||
use super::ToAPI;
|
||||
use crate::library::user::User;
|
||||
use crate::library::{track, Libary};
|
||||
use based::{
|
||||
auth::User,
|
||||
check_admin,
|
||||
request::api::{api_error, to_uuid, FallibleApiResponse, ToAPI},
|
||||
};
|
||||
use fs::NamedFile;
|
||||
use rocket::{fs, get, State};
|
||||
use serde_json::json;
|
||||
|
||||
use crate::check_admin;
|
||||
use crate::library::Libary;
|
||||
|
||||
#[get("/track/<track_id>")]
|
||||
pub async fn track_route(track_id: &str, lib: &State<Libary>) -> FallibleApiResponse {
|
||||
Ok(lib
|
||||
|
@ -38,9 +33,7 @@ pub async fn track_reload_meta_route(
|
|||
|
||||
#[get("/track/<track_id>/audio")]
|
||||
pub async fn track_audio_route(track_id: &str, lib: &State<Libary>) -> Option<NamedFile> {
|
||||
let track = lib
|
||||
.get_track_by_id(&uuid::Uuid::from_str(track_id).ok()?)
|
||||
.await?;
|
||||
let track = lib.get_track_by_id(&to_uuid(track_id).ok()?).await?;
|
||||
NamedFile::open(std::path::Path::new(&track.path))
|
||||
.await
|
||||
.ok()
|
||||
|
@ -48,24 +41,20 @@ pub async fn track_audio_route(track_id: &str, lib: &State<Libary>) -> Option<Na
|
|||
|
||||
#[get("/track/<track_id>/audio/opus128")]
|
||||
pub async fn track_audio_opus128_route(track_id: &str, lib: &State<Libary>) -> Option<NamedFile> {
|
||||
let track = lib
|
||||
.get_track_by_id(&uuid::Uuid::from_str(track_id).ok()?)
|
||||
.await?;
|
||||
let track = lib.get_track_by_id(&to_uuid(track_id).ok()?).await?;
|
||||
NamedFile::open(track.get_opus(128)?).await.ok()
|
||||
}
|
||||
|
||||
#[get("/track/<track_id>/audio/aac128")]
|
||||
pub async fn track_audio_aac128_route(track_id: &str, lib: &State<Libary>) -> Option<NamedFile> {
|
||||
let track = lib
|
||||
.get_track_by_id(&uuid::Uuid::from_str(track_id).ok()?)
|
||||
.await?;
|
||||
let track = lib.get_track_by_id(&to_uuid(track_id).ok()?).await?;
|
||||
NamedFile::open(track.get_aac(128)?).await.ok()
|
||||
}
|
||||
|
||||
#[get("/track/<track_id>/lyrics")]
|
||||
pub async fn track_lyrics_route(track_id: &str, lib: &State<Libary>) -> FallibleApiResponse {
|
||||
let track = lib
|
||||
.get_track_by_id(&uuid::Uuid::from_str(track_id).map_err(|_| no_uuid_error())?)
|
||||
.get_track_by_id(&to_uuid(track_id)?)
|
||||
.await
|
||||
.ok_or_else(|| api_error("No such track"))?;
|
||||
|
||||
|
|
|
@ -1,46 +1,12 @@
|
|||
use crate::get_pg;
|
||||
use crate::library::user::User;
|
||||
use crate::route::vec_to_api;
|
||||
use based::auth::{User, UserRole};
|
||||
use based::check_admin;
|
||||
use based::request::api::{api_error, vec_to_api, FallibleApiResponse};
|
||||
use rocket::get;
|
||||
use rocket::http::Status;
|
||||
use rocket::outcome::Outcome;
|
||||
use rocket::post;
|
||||
use rocket::request::FromRequest;
|
||||
use rocket::serde::json::Json;
|
||||
use rocket::Request;
|
||||
use serde::Deserialize;
|
||||
use serde_json::json;
|
||||
|
||||
use super::api_error;
|
||||
use super::FallibleApiResponse;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! check_admin {
|
||||
($u:ident) => {
|
||||
if !$u.is_admin() {
|
||||
return Err(api_error("Forbidden"));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[rocket::async_trait]
|
||||
impl<'r> FromRequest<'r> for User {
|
||||
type Error = ();
|
||||
|
||||
async fn from_request(request: &'r Request<'_>) -> rocket::request::Outcome<Self, Self::Error> {
|
||||
match request.headers().get_one("token") {
|
||||
Some(key) => {
|
||||
if let Some(user) = sqlx::query_as("SELECT * FROM users WHERE username = (SELECT \"user\" FROM user_session WHERE token = $1)").bind(key).fetch_optional(get_pg!()).await.unwrap() {
|
||||
Outcome::Success(user)
|
||||
} else {
|
||||
Outcome::Error((Status::Unauthorized, ()))
|
||||
}
|
||||
}
|
||||
None => Outcome::Error((Status::Unauthorized, ())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct LoginData {
|
||||
pub username: String,
|
||||
|
@ -89,13 +55,9 @@ pub async fn users_route(u: User) -> FallibleApiResponse {
|
|||
pub async fn user_create_route(user: Json<LoginData>, u: User) -> FallibleApiResponse {
|
||||
check_admin!(u);
|
||||
|
||||
let new_user = User::create(
|
||||
&user.username,
|
||||
&user.password,
|
||||
crate::library::user::UserRole::Regular,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let new_user = User::create(&user.username, &user.password, UserRole::Regular)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
Ok(json!({"created": new_user.username}))
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue