Setup Initial Schema (sqlite) #10
File diff suppressed because it is too large
Load Diff
|
@ -9,4 +9,6 @@ tokio = { version = "1.2", features = ["full"] }
|
||||||
warp = "0.3"
|
warp = "0.3"
|
||||||
rusty-money = "0.4.1"
|
rusty-money = "0.4.1"
|
||||||
dotenv = "0.15.0"
|
dotenv = "0.15.0"
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
serde_json = "1.0"
|
||||||
|
|
||||||
|
|
|
@ -8,4 +8,4 @@ You will need to initialize a sqlite database store: `touch aural_isle.sqlite.db
|
||||||
|
|
||||||
You will need to copy the example env file to .env: `cp env.example .env`
|
You will need to copy the example env file to .env: `cp env.example .env`
|
||||||
|
|
||||||
Then you should be able to run the initial database migrations: `sqlx migrate run`. (During initial dev, if you have already ran the initial migration, you will need to delete the sqlite database store and re-create it as above)
|
Then you should be able to run the initial database migrations: `sqlx migrate run`. (During initial dev, if you have already run the initial migration, you will need to delete the sqlite database store and re-create it as above)
|
24
SCHEMA.md
24
SCHEMA.md
|
@ -24,7 +24,6 @@ CREATE TABLE Persons (
|
||||||
deleted_at INTEGER, /* timestamp */
|
deleted_at INTEGER, /* timestamp */
|
||||||
deleted_by TEXT(36), /* UUIDv4 */
|
deleted_by TEXT(36), /* UUIDv4 */
|
||||||
last_seen INTEGER, /* timestamp */
|
last_seen INTEGER, /* timestamp */
|
||||||
shipping_address TEXT, /* optional, should use \n between lines to keep localized format as needed */
|
|
||||||
CONSTRAINT Persons_PK PRIMARY KEY (id),
|
CONSTRAINT Persons_PK PRIMARY KEY (id),
|
||||||
CONSTRAINT Persons_FK FOREIGN KEY (modified_by) REFERENCES Persons(id),
|
CONSTRAINT Persons_FK FOREIGN KEY (modified_by) REFERENCES Persons(id),
|
||||||
CONSTRAINT Persons_FK_1 FOREIGN KEY (deleted_by) REFERENCES Persons(id)
|
CONSTRAINT Persons_FK_1 FOREIGN KEY (deleted_by) REFERENCES Persons(id)
|
||||||
|
@ -37,6 +36,28 @@ CREATE INDEX Persons_is_blocked_IDX ON Persons (is_blocked);
|
||||||
CREATE INDEX Persons_deleted_at_IDX ON Persons (deleted_at);
|
CREATE INDEX Persons_deleted_at_IDX ON Persons (deleted_at);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
# PersonShippingAddresses
|
||||||
|
The PersonShippingAddresses table will contain zero or more shipping addresses as used by a Person
|
||||||
|
|
||||||
|
``` sql
|
||||||
|
CREATE TABLE IF NOT EXISTS PersonShippingAddresses (
|
||||||
|
id TEXT(36), /* UUIDv4 */
|
||||||
|
person_id TEXT(36), /* UUIDv4 */
|
||||||
|
shipping_address TEXT, /* mandatory, should use \n between lines to keep localized format as needed */
|
||||||
|
is_primary INTEGER DEFAULT (0), /* bool, default false */
|
||||||
|
created_at INTEGER, /* timestamp */
|
||||||
|
modified_at INTEGER, /* timestamp */
|
||||||
|
modified_by TEXT(36), /* UUIDv4 */
|
||||||
|
deleted_at INTEGER, /* timestamp */
|
||||||
|
deleted_by TEXT(36), /* UUIDv4 */
|
||||||
|
CONSTRAINT PersonShippingAddresses_PK PRIMARY KEY (id),
|
||||||
|
CONSTRAINT PersonShippingAddresses_FK FOREIGN KEY (person_id) REFERENCES Persons(id),
|
||||||
|
CONSTRAINT PersonShippingAddresses_FK_1 FOREIGN KEY (modified_by) REFERENCES Persons(id),
|
||||||
|
CONSTRAINT PersonShippingAddresses_FK_2 FOREIGN KEY (deleted_by) REFERENCES Persons(id)
|
||||||
|
);
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS PersonShippingAddresses_is_primary_IDX ON PersonShippingAddresses (is_primary);
|
||||||
|
```
|
||||||
|
|
||||||
# PersonEmails Table
|
# PersonEmails Table
|
||||||
The PersonEmails table is a one-to-many lookup table relating a Person to zero or more email addresses
|
The PersonEmails table is a one-to-many lookup table relating a Person to zero or more email addresses
|
||||||
|
|
||||||
|
@ -191,6 +212,7 @@ The Artists table will contain Artists!
|
||||||
``` sql
|
``` sql
|
||||||
CREATE TABLE Artists (
|
CREATE TABLE Artists (
|
||||||
id TEXT(36),
|
id TEXT(36),
|
||||||
|
label_id TEXT(36),
|
||||||
name TEXT,
|
name TEXT,
|
||||||
bio TEXT,
|
bio TEXT,
|
||||||
website TEXT,
|
website TEXT,
|
||||||
|
|
|
@ -168,6 +168,7 @@ CREATE INDEX IF NOT EXISTS LabelTags_label_id_IDX ON LabelTags (label_id);
|
||||||
-- The Artists table will contain Artists!
|
-- The Artists table will contain Artists!
|
||||||
CREATE TABLE IF NOT EXISTS Artists (
|
CREATE TABLE IF NOT EXISTS Artists (
|
||||||
id TEXT(36),
|
id TEXT(36),
|
||||||
|
label_id TEXT(36),
|
||||||
name TEXT,
|
name TEXT,
|
||||||
bio TEXT,
|
bio TEXT,
|
||||||
website TEXT,
|
website TEXT,
|
||||||
|
|
564
src/main.rs
564
src/main.rs
|
@ -1,9 +1,565 @@
|
||||||
use warp::Filter;
|
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]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
let hello = warp::get()
|
let datastore = Datastore::new();
|
||||||
.map(|| format!("Hello world!"));
|
let datastore_filter = warp::any().map(move || datastore.clone());
|
||||||
|
|
||||||
warp::serve(hello).run(([127, 0, 0, 1], 5309)).await;
|
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;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue