aural_isle/src/main.rs

566 lines
15 KiB
Rust

use serde::{Deserialize, Serialize};
use std::{collections::HashMap, sync::Arc};
use warp::{
filters::{body::BodyDeserializeError, cors::CorsForbidden},
http::Method,
http::StatusCode,
reject::Reject,
Filter, Rejection, Reply,
};
use tokio::sync::RwLock;
#[derive(Deserialize, Serialize, Debug, Clone)]
struct Person {
id: PersonId,
remote_id: String,
name: String,
handle: Option<String>,
avatar: Option<String>,
cover: Option<String>,
bio: Option<String>,
is_active: bool,
is_blocked: bool,
created_at: usize,
modified_at: usize,
deleted_at: Option<usize>,
modified_by: PersonId,
deleted_by: Option<PersonId>,
last_seen: usize,
shipping_addresses: Option<Vec<PersonShippingAddress>>,
emails: Option<Vec<PersonEmail>>,
credentials: Option<Vec<PersonCredential>>,
}
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, Hash)]
struct PersonId(String);
#[derive(Deserialize, Serialize, Debug, Clone)]
struct PersonShippingAddress {
id: PersonShippingAddressId,
person_id: PersonId,
shipping_address: String,
is_primary: bool,
created_at: usize,
modified_at: usize,
deleted_at: Option<usize>,
modified_by: PersonId,
deleted_by: Option<PersonId>,
}
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, Hash)]
struct PersonShippingAddressId(String);
#[derive(Deserialize, Serialize, Debug, Clone)]
struct PersonEmail {
person_id: PersonId,
email: String,
is_verified: bool,
is_primary: bool,
}
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, Hash)]
struct PersonEmailId(String);
#[derive(Deserialize, Serialize, Debug, Clone)]
struct PersonCredentialProvider {
id: PersonCredentialProviderId,
name: String,
r#type: String, // TODO: create a type for this
config: String,
}
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, Hash)]
struct PersonCredentialProviderId(String);
#[derive(Deserialize, Serialize, Debug, Clone)]
struct PersonCredential {
id: PersonCredentialId,
person_id: PersonId,
provider_user_id: String,
is_enabled: bool,
}
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, Hash)]
struct PersonCredentialId(String);
#[derive(Deserialize, Serialize, Debug, Clone)]
struct Tag {
id: TagId,
host: String,
tag: String,
}
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, Hash)]
struct TagId(String);
#[derive(Deserialize, Serialize, Debug, Clone)]
struct Label {
id: LabelId,
name: String,
description: String,
website: String,
is_enabled: bool,
created_at: usize,
modified_at: usize,
deleted_at: Option<usize>,
created_by: PersonId,
modified_by: PersonId,
deleted_by: Option<PersonId>,
contacts: Option<Vec<LabelContact>>,
tags: Option<Vec<Tag>>,
artists: Option<Vec<Artist>>,
}
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, Hash)]
struct LabelId(String);
#[derive(Deserialize, Serialize, Debug, Clone)]
struct LabelContact {
id: LabelContactId,
label_id: LabelId,
method: String, // TODO: create a type for this
address: String,
created_at: usize,
modified_at: usize,
deleted_at: Option<usize>,
created_by: PersonId,
modified_by: PersonId,
deleted_by: Option<PersonId>,
sort_order: usize,
}
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, Hash)]
struct LabelContactId(String);
#[derive(Deserialize, Serialize, Debug, Clone)]
struct Artist {
id: ArtistId,
label_id: Option<LabelId>,
name: String,
bio: String,
website: String,
created_at: usize,
modified_at: usize,
deleted_at: Option<usize>,
created_by: PersonId,
modified_by: PersonId,
deleted_by: Option<PersonId>,
is_enabled: bool,
is_public: bool,
contacts: Option<Vec<ArtistContact>>,
tags: Option<Vec<Tag>>,
}
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, Hash)]
struct ArtistId(String);
#[derive(Deserialize, Serialize, Debug, Clone)]
struct ArtistContact {
id: ArtistContactId,
artist_id: ArtistId,
method: String, // TODO: create a type for this
address: String,
created_at: usize,
modified_at: usize,
deleted_at: Option<usize>,
created_by: PersonId,
modified_by: PersonId,
deleted_by: Option<PersonId>,
sort_order: usize,
}
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, Hash)]
struct ArtistContactId(String);
#[derive(Deserialize, Serialize, Debug, Clone)]
struct Track {
id: TrackId,
title: String,
description: String,
duration: f64,
is_public: bool,
is_available: bool,
preview_source: Option<String>, // if preview_source == None, use source as preview
source: String,
price: usize,
created_at: usize,
modified_at: usize,
deleted_at: Option<usize>,
created_by: PersonId,
modified_by: PersonId,
deleted_by: Option<PersonId>,
lyrics: Option<String>,
primary_artist: Artist,
other_artists: Option<Vec<Artist>>,
tags: Option<Vec<Tag>>,
}
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, Hash)]
struct TrackId(String);
#[derive(Deserialize, Serialize, Debug, Clone)]
struct Album {
id: AlbumId,
title: String,
description: String,
tracks: Vec<Track>,
is_public: bool,
is_available: bool,
preview_source: Option<String>,
source: String,
price: usize,
created_at: usize,
modified_at: usize,
deleted_at: Option<usize>,
created_by: PersonId,
modified_by: PersonId,
deleted_by: Option<PersonId>,
primary_artist: Option<Artist>,
other_artists: Option<Vec<Artist>>,
tags: Option<Vec<Tag>>,
}
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, Hash)]
struct AlbumId(String);
#[derive(Deserialize, Serialize, Debug, Clone)]
struct Playlist {
id: PlaylistId,
title: String,
description: String,
created_by: PersonId,
created_at: usize,
modified_at: usize,
deleted_at: Option<usize>,
is_public: bool,
tracks: Option<Vec<Track>>,
tags: Option<Vec<Tag>>,
}
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, Hash)]
struct PlaylistId(String);
#[derive(Debug)]
struct Pagination {
start: usize,
end: usize,
}
#[derive(Clone)]
struct Datastore {
persons: Arc<RwLock<HashMap<PersonId, Person>>>,
labels: Arc<RwLock<HashMap<LabelId, Label>>>,
artists: Arc<RwLock<HashMap<ArtistId, Artist>>>,
albums: Arc<RwLock<HashMap<AlbumId, Album>>>,
tracks: Arc<RwLock<HashMap<TrackId, Track>>>,
}
impl Datastore {
fn new() -> Self {
Datastore {
persons: Arc::new(RwLock::new(HashMap::new())),
labels: Arc::new(RwLock::new(HashMap::new())),
artists: Arc::new(RwLock::new(HashMap::new())),
albums: Arc::new(RwLock::new(HashMap::new())),
tracks: Arc::new(RwLock::new(HashMap::new())),
}
}
}
#[derive(Debug)]
enum Error {
ParseError(std::num::ParseIntError),
MissingParameters,
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match *self {
Error::ParseError(ref err) => write!(f, "Cannot parse parameter: {}", err),
Error::MissingParameters => write!(f, "Missing parameter"),
}
}
}
impl Reject for Error {}
async fn return_error(r: Rejection) -> Result<impl Reply, Rejection> {
if let Some(error) = r.find::<Error>() {
Ok(warp::reply::with_status(
error.to_string(),
StatusCode::RANGE_NOT_SATISFIABLE,
))
} else if let Some(error) = r.find::<CorsForbidden>() {
Ok(warp::reply::with_status(
error.to_string(),
StatusCode::FORBIDDEN,
))
} else if let Some(error) = r.find::<BodyDeserializeError>() {
Ok(warp::reply::with_status(
error.to_string(),
StatusCode::UNPROCESSABLE_ENTITY,
))
} else {
Ok(warp::reply::with_status(
"Route not found".to_string(),
StatusCode::NOT_FOUND,
))
}
}
fn extract_pagination(params: HashMap<String, String>) -> Result<Pagination, Error> {
if params.contains_key("start") && params.contains_key("end") {
return Ok(Pagination {
start: params
.get("start")
.unwrap()
.parse::<usize>()
.map_err(Error::ParseError)?,
end: params
.get("end")
.unwrap()
.parse::<usize>()
.map_err(Error::ParseError)?,
});
}
Err(Error::MissingParameters)
}
async fn get_persons(
params: HashMap<String, String>,
datastore: Datastore,
) -> Result<impl warp::Reply, warp::Rejection> {
if !params.is_empty() {
let pagination = extract_pagination(params)?;
let res: Vec<Person> = datastore.persons.read().await.values().cloned().collect();
let res = &res[pagination.start..pagination.end];
Ok(warp::reply::json(&res))
} else {
let res: Vec<Person> = datastore.persons.read().await.values().cloned().collect();
Ok(warp::reply::json(&res))
}
}
async fn get_labels(
params: HashMap<String, String>,
datastore: Datastore,
) -> Result<impl warp::Reply, warp::Rejection> {
if !params.is_empty() {
let pagination = extract_pagination(params)?;
let res: Vec<Label> = datastore.labels.read().await.values().cloned().collect();
let res = &res[pagination.start..pagination.end];
Ok(warp::reply::json(&res))
} else {
let res: Vec<Label> = datastore.labels.read().await.values().cloned().collect();
Ok(warp::reply::json(&res))
}
}
async fn get_artists(
params: HashMap<String, String>,
datastore: Datastore,
) -> Result<impl warp::Reply, warp::Rejection> {
if !params.is_empty() {
let pagination = extract_pagination(params)?;
let res: Vec<Artist> = datastore.artists.read().await.values().cloned().collect();
let res = &res[pagination.start..pagination.end];
Ok(warp::reply::json(&res))
} else {
let res: Vec<Artist> = datastore.artists.read().await.values().cloned().collect();
Ok(warp::reply::json(&res))
}
}
async fn get_albums(
params: HashMap<String, String>,
datastore: Datastore,
) -> Result<impl warp::Reply, warp::Rejection> {
if !params.is_empty() {
let pagination = extract_pagination(params)?;
let res: Vec<Album> = datastore.albums.read().await.values().cloned().collect();
let res = &res[pagination.start..pagination.end];
Ok(warp::reply::json(&res))
} else {
let res: Vec<Album> = datastore.albums.read().await.values().cloned().collect();
Ok(warp::reply::json(&res))
}
}
async fn get_tracks(
params: HashMap<String, String>,
datastore: Datastore,
) -> Result<impl warp::Reply, warp::Rejection> {
if !params.is_empty() {
let pagination = extract_pagination(params)?;
let res: Vec<Track> = datastore.tracks.read().await.values().cloned().collect();
let res = &res[pagination.start..pagination.end];
Ok(warp::reply::json(&res))
} else {
let res: Vec<Track> = datastore.tracks.read().await.values().cloned().collect();
Ok(warp::reply::json(&res))
}
}
async fn add_person(
datastore: Datastore,
person: Person,
) -> Result<impl warp::Reply, warp::Rejection> {
datastore
.persons
.write()
.await
.insert(person.id.clone(), person);
Ok(warp::reply::with_status("Person added", StatusCode::OK))
}
async fn add_label(
datastore: Datastore,
label: Label,
) -> Result<impl warp::Reply, warp::Rejection> {
datastore
.labels
.write()
.await
.insert(label.id.clone(), label);
Ok(warp::reply::with_status("Label added", StatusCode::OK))
}
async fn add_artist(
datastore: Datastore,
artist: Artist,
) -> Result<impl warp::Reply, warp::Rejection> {
datastore
.artists
.write()
.await
.insert(artist.id.clone(), artist);
Ok(warp::reply::with_status("Artist added", StatusCode::OK))
}
async fn add_album(
datastore: Datastore,
album: Album,
) -> Result<impl warp::Reply, warp::Rejection> {
datastore
.albums
.write()
.await
.insert(album.id.clone(), album);
Ok(warp::reply::with_status("Album added", StatusCode::OK))
}
async fn add_track(
datastore: Datastore,
track: Track,
) -> Result<impl warp::Reply, warp::Rejection> {
datastore
.tracks
.write()
.await
.insert(track.id.clone(), track);
Ok(warp::reply::with_status("Track added", StatusCode::OK))
}
#[tokio::main]
async fn main() {
let datastore = Datastore::new();
let datastore_filter = warp::any().map(move || datastore.clone());
let cors = warp::cors()
.allow_any_origin()
.allow_header("content-type")
.allow_methods(&[Method::PUT, Method::DELETE, Method::GET, Method::POST]);
let get_persons = warp::get()
.and(warp::path("persons"))
.and(warp::path::end())
.and(warp::query())
.and(datastore_filter.clone())
.and_then(get_persons);
let get_labels = warp::get()
.and(warp::path("labels"))
.and(warp::path::end())
.and(warp::query())
.and(datastore_filter.clone())
.and_then(get_labels);
let get_artists = warp::get()
.and(warp::path("artists"))
.and(warp::path::end())
.and(warp::query())
.and(datastore_filter.clone())
.and_then(get_artists);
let get_albums = warp::get()
.and(warp::path("albums"))
.and(warp::path::end())
.and(warp::query())
.and(datastore_filter.clone())
.and_then(get_albums);
let get_tracks = warp::get()
.and(warp::path("tracks"))
.and(warp::path::end())
.and(warp::query())
.and(datastore_filter.clone())
.and_then(get_tracks);
let add_person = warp::post()
.and(warp::path("persons"))
.and(warp::path::end())
.and(datastore_filter.clone())
.and(warp::body::json())
.and_then(add_person);
let add_label = warp::post()
.and(warp::path("labels"))
.and(warp::path::end())
.and(datastore_filter.clone())
.and(warp::body::json())
.and_then(add_label);
let add_artist = warp::post()
.and(warp::path("artists"))
.and(warp::path::end())
.and(datastore_filter.clone())
.and(warp::body::json())
.and_then(add_artist);
let add_album = warp::post()
.and(warp::path("albums"))
.and(warp::path::end())
.and(datastore_filter.clone())
.and(warp::body::json())
.and_then(add_album);
let add_track = warp::post()
.and(warp::path("tracks"))
.and(warp::path::end())
.and(datastore_filter.clone())
.and(warp::body::json())
.and_then(add_track);
let routes = get_persons
.or(get_labels)
.or(get_artists)
.or(get_albums)
.or(get_tracks)
.or(add_person)
.or(add_label)
.or(add_artist)
.or(add_album)
.or(add_track)
.with(cors)
.recover(return_error);
warp::serve(routes).run(([127, 0, 0, 1], 5309)).await;
}