Compare commits
2 commits
fdf3fa5904
...
044ccd6683
Author | SHA1 | Date | |
---|---|---|---|
044ccd6683 | |||
f67c23d9e7 |
28 changed files with 779 additions and 631 deletions
13
Cargo.lock
generated
13
Cargo.lock
generated
|
@ -199,6 +199,7 @@ checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf"
|
|||
dependencies = [
|
||||
"async-trait",
|
||||
"axum-core",
|
||||
"axum-macros",
|
||||
"bitflags 1.3.2",
|
||||
"bytes",
|
||||
"futures-util",
|
||||
|
@ -240,6 +241,18 @@ dependencies = [
|
|||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum-macros"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cdca6a10ecad987bda04e95606ef85a5417dcaac1a78455242d72e031e2b6b62"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.39",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.69"
|
||||
|
|
|
@ -73,7 +73,7 @@ sqlx = { version = "0.7.0", default-features = false, features = [
|
|||
] }
|
||||
|
||||
# Web server
|
||||
axum = "0.6.20"
|
||||
axum = { version = "0.6.20", features = ["macros"] }
|
||||
headers = "0.3.9"
|
||||
http = "0.2.9"
|
||||
hyper = { version = "0.14.27", features = ["stream"] }
|
||||
|
|
|
@ -16,7 +16,7 @@ pub struct ApiError {
|
|||
}
|
||||
|
||||
/// API error type
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[cfg_attr(feature = "utoipa", derive(ToSchema))]
|
||||
pub enum ApiErrorKind {
|
||||
|
|
|
@ -20,6 +20,12 @@ pub enum ApiError {
|
|||
NotFound(Cow<'static, str>),
|
||||
#[error("{0}")]
|
||||
Other(Cow<'static, str>),
|
||||
#[error("{msg}")]
|
||||
Custom {
|
||||
msg: Cow<'static, str>,
|
||||
status: StatusCode,
|
||||
kind: ApiErrorKind,
|
||||
},
|
||||
}
|
||||
|
||||
impl ErrorStatus for ApiError {
|
||||
|
@ -32,6 +38,7 @@ impl ErrorStatus for ApiError {
|
|||
ApiError::Input(_) => StatusCode::BAD_REQUEST,
|
||||
ApiError::NotFound(_) => StatusCode::NOT_FOUND,
|
||||
ApiError::Other(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
ApiError::Custom { status, .. } => *status,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -44,6 +51,7 @@ impl ErrorStatus for ApiError {
|
|||
ApiError::Input(_) => ApiErrorKind::User,
|
||||
ApiError::NotFound(_) => ApiErrorKind::User,
|
||||
ApiError::Other(_) => ApiErrorKind::Other,
|
||||
ApiError::Custom { kind, .. } => *kind,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
65
crates/api/src/extract.rs
Normal file
65
crates/api/src/extract.rs
Normal file
|
@ -0,0 +1,65 @@
|
|||
//! These are custom axum extractors which return proper ApiErrors when they receive invalid data
|
||||
//!
|
||||
//! Source of example: https://github.com/tokio-rs/axum/blob/3ff45d9c96b5192af6b6ec26eb2a2bfcddd00d7d/examples/customize-extractor-error/src/derive_from_request.rs
|
||||
|
||||
use axum::{
|
||||
extract::FromRequest,
|
||||
extract::{
|
||||
rejection::{JsonRejection, PathRejection, QueryRejection},
|
||||
FromRequestParts,
|
||||
},
|
||||
response::IntoResponse,
|
||||
};
|
||||
use serde::Serialize;
|
||||
use tiraya_api_model::ApiErrorKind;
|
||||
|
||||
use crate::error::ApiError;
|
||||
|
||||
#[derive(FromRequestParts)]
|
||||
#[from_request(via(axum::extract::Path), rejection(ApiError))]
|
||||
pub struct Path<T>(pub T);
|
||||
|
||||
#[derive(FromRequestParts)]
|
||||
#[from_request(via(axum::extract::Query), rejection(ApiError))]
|
||||
pub struct Query<T>(pub T);
|
||||
|
||||
#[derive(FromRequest)]
|
||||
#[from_request(via(axum::extract::Json), rejection(ApiError))]
|
||||
pub struct Json<T>(pub T);
|
||||
|
||||
impl<T: Serialize> IntoResponse for Json<T> {
|
||||
fn into_response(self) -> axum::response::Response {
|
||||
let Self(value) = self;
|
||||
axum::Json(value).into_response()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PathRejection> for ApiError {
|
||||
fn from(rejection: PathRejection) -> Self {
|
||||
Self::Custom {
|
||||
msg: rejection.body_text().into(),
|
||||
status: rejection.status(),
|
||||
kind: ApiErrorKind::User,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<QueryRejection> for ApiError {
|
||||
fn from(rejection: QueryRejection) -> Self {
|
||||
Self::Custom {
|
||||
msg: rejection.body_text().into(),
|
||||
status: rejection.status(),
|
||||
kind: ApiErrorKind::User,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<JsonRejection> for ApiError {
|
||||
fn from(rejection: JsonRejection) -> Self {
|
||||
Self::Custom {
|
||||
msg: rejection.body_text().into(),
|
||||
status: rejection.status(),
|
||||
kind: ApiErrorKind::User,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
mod error;
|
||||
mod extract;
|
||||
mod routes;
|
||||
|
||||
use std::{net::SocketAddr, ops::Deref, str::FromStr, sync::Arc};
|
||||
|
@ -130,7 +131,10 @@ pub async fn serve() -> Result<(), anyhow::Error> {
|
|||
.route(
|
||||
"/user/:id/playlists",
|
||||
routing::get(routes::user::get_user_playlists),
|
||||
),
|
||||
)
|
||||
.fallback(|| async {
|
||||
crate::error::ApiError::NotFound("API endpoint not found".into())
|
||||
}),
|
||||
)
|
||||
// TMP: move to frontend server
|
||||
.merge(utoipa_rapidoc::RapiDoc::new("/api/openapi.json").path("/api-docs"))
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
use axum::{
|
||||
extract::{Path, Query, State},
|
||||
Json,
|
||||
};
|
||||
use axum::extract::State;
|
||||
use serde::Deserialize;
|
||||
use tiraya_api_model::{Album, TrackSlim};
|
||||
use tiraya_db::models::{self as tdb};
|
||||
use tiraya_extractor::parse_validate_tid;
|
||||
use tiraya_utils::EntityType;
|
||||
|
||||
use crate::{error::ApiError, ApiState};
|
||||
use crate::{
|
||||
error::ApiError,
|
||||
extract::{Json, Path, Query},
|
||||
ApiState,
|
||||
};
|
||||
|
||||
#[derive(Default, Deserialize)]
|
||||
#[serde(default)]
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
use axum::{
|
||||
extract::{Path, Query, State},
|
||||
Json,
|
||||
};
|
||||
use axum::extract::State;
|
||||
use serde::Deserialize;
|
||||
use tiraya_api_model::{AlbumSlim, Artist, ArtistSlim, PlaylistSlim, TrackSlim};
|
||||
use tiraya_db::models::{self as tdb};
|
||||
use tiraya_extractor::parse_validate_tid;
|
||||
use tiraya_utils::EntityType;
|
||||
|
||||
use crate::{error::ApiError, ApiState};
|
||||
use crate::{
|
||||
error::ApiError,
|
||||
extract::{Json, Path, Query},
|
||||
ApiState,
|
||||
};
|
||||
|
||||
/// Get artist
|
||||
///
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
use axum::extract::{Path, Query, State};
|
||||
use axum::extract::State;
|
||||
use hyper::{Body, Response};
|
||||
use serde::Deserialize;
|
||||
use tiraya_db::models::{self as tdb};
|
||||
use tiraya_extractor::parse_validate_tid;
|
||||
use tiraya_utils::{ImageKind, ImageSize};
|
||||
|
||||
use crate::{error::ApiError, ApiState};
|
||||
use crate::{
|
||||
error::ApiError,
|
||||
extract::{Path, Query},
|
||||
ApiState,
|
||||
};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct LocalImageQuery {
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use axum::{
|
||||
extract::{Path, Query, State},
|
||||
Json,
|
||||
};
|
||||
use axum::extract::State;
|
||||
use serde::Deserialize;
|
||||
use tiraya_api_model::{Playlist, UserSlim};
|
||||
use tiraya_db::models::{self as tdb};
|
||||
|
@ -11,7 +8,11 @@ use tiraya_extractor::parse_validate_tid;
|
|||
use tiraya_utils::EntityType;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{error::ApiError, ApiState};
|
||||
use crate::{
|
||||
error::ApiError,
|
||||
extract::{Json, Path, Query},
|
||||
ApiState,
|
||||
};
|
||||
|
||||
#[derive(Default, Deserialize)]
|
||||
#[serde(default)]
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
use axum::{
|
||||
extract::{Path, State},
|
||||
Json,
|
||||
};
|
||||
use axum::extract::State;
|
||||
use tiraya_api_model::Track;
|
||||
use tiraya_extractor::parse_validate_tid;
|
||||
use tiraya_utils::EntityType;
|
||||
|
||||
use crate::{error::ApiError, ApiState};
|
||||
use crate::{
|
||||
error::ApiError,
|
||||
extract::{Json, Path},
|
||||
ApiState,
|
||||
};
|
||||
|
||||
/// Get track
|
||||
///
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
use axum::{
|
||||
extract::{Path, State},
|
||||
Json,
|
||||
};
|
||||
use axum::extract::State;
|
||||
use tiraya_api_model::{PlaylistSlim, User};
|
||||
use tiraya_db::models::{self as tdb};
|
||||
use tiraya_extractor::parse_validate_tid;
|
||||
use tiraya_utils::EntityType;
|
||||
|
||||
use crate::{error::ApiError, ApiState};
|
||||
use crate::{
|
||||
error::ApiError,
|
||||
extract::{Json, Path},
|
||||
ApiState,
|
||||
};
|
||||
|
||||
/// Get user
|
||||
///
|
||||
|
|
|
@ -1,45 +0,0 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "insert into tracks (src_id, service, name, duration,\n album_id, album_pos, ul_artists, isrc, description, file_size, track_gain, primary_track)\nvalues ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)\non conflict (src_id, service) do update set\n name = excluded.name,\n duration = coalesce(excluded.duration, tracks.duration),\n album_id = excluded.album_id,\n album_pos = coalesce(excluded.album_pos, tracks.album_pos),\n ul_artists = coalesce(excluded.ul_artists, tracks.ul_artists),\n isrc = coalesce(excluded.isrc, tracks.isrc),\n description = coalesce(excluded.description, tracks.description),\n file_size = coalesce(excluded.file_size, tracks.file_size),\n track_gain = coalesce(excluded.track_gain, tracks.track_gain),\n primary_track = coalesce(excluded.primary_track, tracks.primary_track)\nreturning id",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
{
|
||||
"Custom": {
|
||||
"name": "music_service",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"ty",
|
||||
"yt",
|
||||
"sp",
|
||||
"mx"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"Text",
|
||||
"Int4",
|
||||
"Int4",
|
||||
"Int2",
|
||||
"TextArray",
|
||||
"Varchar",
|
||||
"Text",
|
||||
"Int8",
|
||||
"Float4",
|
||||
"Bool"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "2c9bace9d693f5c7af4fa3ab77f021d399974387e2be0ca0107c8706a0a71d82"
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "select t.id, t.src_id, t.service as \"service: _\", t.name, t.duration,\n b.id as album_id, b.src_id as album_src_id, b.service as \"album_service: _\", b.name as album_name, b.release_date,\n b.album_type as \"album_type: _\", b.image_url, b.image_date,\n t.album_pos, t.ul_artists, t.isrc, t.description, t.file_size, t.track_gain, t.created_at, t.updated_at,\n t.primary_track, t.downloaded_at, t.last_streamed_at, t.n_streams,\n jsonb_agg(json_build_object('id', a.src_id, 'sv', a.service, 'n', a.name) order by art.seq)\n filter (where a.src_id is not null) as \"artists: _\"\nfrom tracks t\n left join artists_tracks art on art.track_id = t.id\n left join artists a on a.id = art.artist_id\n left join albums b on b.id = t.album_id\n join track_aliases ta on ta.track_id=t.id\nwhere ta.src_id=$1 and ta.service=$2\ngroup by (t.id, b.id)",
|
||||
"query": "select t.id, t.src_id, t.service as \"service: _\", t.name, t.duration, t.duration_ms,\n b.id as album_id, b.src_id as album_src_id, b.service as \"album_service: _\", b.name as album_name, b.release_date,\n b.album_type as \"album_type: _\", b.image_url, b.image_date,\n t.album_pos, t.ul_artists, t.isrc, t.description, t.file_size, t.track_gain, t.created_at, t.updated_at,\n t.primary_track, t.downloaded_at, t.last_streamed_at, t.n_streams,\n jsonb_agg(json_build_object('id', a.src_id, 'sv', a.service, 'n', a.name) order by art.seq)\n filter (where a.src_id is not null) as \"artists: _\"\nfrom tracks t\n left join artists_tracks art on art.track_id = t.id\n left join artists a on a.id = art.artist_id\n left join albums b on b.id = t.album_id\n join track_aliases ta on ta.track_id=t.id\nwhere ta.src_id=$1 and ta.service=$2\ngroup by (t.id, b.id)",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
|
@ -42,16 +42,21 @@
|
|||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "duration_ms",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "album_id",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"ordinal": 7,
|
||||
"name": "album_src_id",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"ordinal": 8,
|
||||
"name": "album_service: _",
|
||||
"type_info": {
|
||||
"Custom": {
|
||||
|
@ -68,17 +73,17 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"ordinal": 9,
|
||||
"name": "album_name",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"ordinal": 10,
|
||||
"name": "release_date",
|
||||
"type_info": "Date"
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"ordinal": 11,
|
||||
"name": "album_type: _",
|
||||
"type_info": {
|
||||
"Custom": {
|
||||
|
@ -95,77 +100,77 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"ordinal": 12,
|
||||
"name": "image_url",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"ordinal": 13,
|
||||
"name": "image_date",
|
||||
"type_info": "Timestamp"
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"ordinal": 14,
|
||||
"name": "album_pos",
|
||||
"type_info": "Int2"
|
||||
},
|
||||
{
|
||||
"ordinal": 14,
|
||||
"ordinal": 15,
|
||||
"name": "ul_artists",
|
||||
"type_info": "TextArray"
|
||||
},
|
||||
{
|
||||
"ordinal": 15,
|
||||
"ordinal": 16,
|
||||
"name": "isrc",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 16,
|
||||
"ordinal": 17,
|
||||
"name": "description",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 17,
|
||||
"ordinal": 18,
|
||||
"name": "file_size",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 18,
|
||||
"ordinal": 19,
|
||||
"name": "track_gain",
|
||||
"type_info": "Float4"
|
||||
},
|
||||
{
|
||||
"ordinal": 19,
|
||||
"ordinal": 20,
|
||||
"name": "created_at",
|
||||
"type_info": "Timestamp"
|
||||
},
|
||||
{
|
||||
"ordinal": 20,
|
||||
"ordinal": 21,
|
||||
"name": "updated_at",
|
||||
"type_info": "Timestamp"
|
||||
},
|
||||
{
|
||||
"ordinal": 21,
|
||||
"ordinal": 22,
|
||||
"name": "primary_track",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 22,
|
||||
"ordinal": 23,
|
||||
"name": "downloaded_at",
|
||||
"type_info": "Timestamp"
|
||||
},
|
||||
{
|
||||
"ordinal": 23,
|
||||
"ordinal": 24,
|
||||
"name": "last_streamed_at",
|
||||
"type_info": "Timestamp"
|
||||
},
|
||||
{
|
||||
"ordinal": 24,
|
||||
"ordinal": 25,
|
||||
"name": "n_streams",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 25,
|
||||
"ordinal": 26,
|
||||
"name": "artists: _",
|
||||
"type_info": "Jsonb"
|
||||
}
|
||||
|
@ -198,6 +203,7 @@
|
|||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
|
@ -217,5 +223,5 @@
|
|||
null
|
||||
]
|
||||
},
|
||||
"hash": "6cb7208a08d2854472551fa8a771ea47ffc5bf510676b04fb487702ea08673ed"
|
||||
"hash": "5178a0126bd22c1508c7dd630e38213052bf6f8623e59147c38f00d0acf38ff4"
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "select t.id, t.src_id, t.service as \"service: _\", t.name, t.duration,\n b.id as album_id, b.src_id as album_src_id, b.service as \"album_service: _\", b.name as album_name, b.release_date,\n b.album_type as \"album_type: _\", b.image_url, b.image_date,\n t.album_pos, t.ul_artists, t.isrc, t.description, t.file_size, t.track_gain, t.created_at, t.updated_at,\n t.primary_track, t.downloaded_at, t.last_streamed_at, t.n_streams,\n jsonb_agg(json_build_object('id', a.src_id, 'sv', a.service, 'n', a.name) order by art.seq)\n filter (where a.src_id is not null) as \"artists: _\"\nfrom tracks t\n left join artists_tracks art on art.track_id = t.id\n left join artists a on a.id = art.artist_id\n left join albums b on b.id = t.album_id\nwhere t.src_id=$1 and t.service=$2\ngroup by (t.id, b.id)",
|
||||
"query": "select t.id, t.src_id, t.service as \"service: _\", t.name, t.duration, t.duration_ms,\n b.id as album_id, b.src_id as album_src_id, b.service as \"album_service: _\", b.name as album_name, b.release_date,\n b.album_type as \"album_type: _\", b.image_url, b.image_date,\n t.album_pos, t.ul_artists, t.isrc, t.description, t.file_size, t.track_gain, t.created_at, t.updated_at,\n t.primary_track, t.downloaded_at, t.last_streamed_at, t.n_streams,\n jsonb_agg(json_build_object('id', a.src_id, 'sv', a.service, 'n', a.name) order by art.seq)\n filter (where a.src_id is not null) as \"artists: _\"\nfrom tracks t\n left join artists_tracks art on art.track_id = t.id\n left join artists a on a.id = art.artist_id\n left join albums b on b.id = t.album_id\nwhere t.src_id=$1 and t.service=$2\ngroup by (t.id, b.id)",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
|
@ -42,16 +42,21 @@
|
|||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "duration_ms",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "album_id",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"ordinal": 7,
|
||||
"name": "album_src_id",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"ordinal": 8,
|
||||
"name": "album_service: _",
|
||||
"type_info": {
|
||||
"Custom": {
|
||||
|
@ -68,17 +73,17 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"ordinal": 9,
|
||||
"name": "album_name",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"ordinal": 10,
|
||||
"name": "release_date",
|
||||
"type_info": "Date"
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"ordinal": 11,
|
||||
"name": "album_type: _",
|
||||
"type_info": {
|
||||
"Custom": {
|
||||
|
@ -95,77 +100,77 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"ordinal": 12,
|
||||
"name": "image_url",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"ordinal": 13,
|
||||
"name": "image_date",
|
||||
"type_info": "Timestamp"
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"ordinal": 14,
|
||||
"name": "album_pos",
|
||||
"type_info": "Int2"
|
||||
},
|
||||
{
|
||||
"ordinal": 14,
|
||||
"ordinal": 15,
|
||||
"name": "ul_artists",
|
||||
"type_info": "TextArray"
|
||||
},
|
||||
{
|
||||
"ordinal": 15,
|
||||
"ordinal": 16,
|
||||
"name": "isrc",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 16,
|
||||
"ordinal": 17,
|
||||
"name": "description",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 17,
|
||||
"ordinal": 18,
|
||||
"name": "file_size",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 18,
|
||||
"ordinal": 19,
|
||||
"name": "track_gain",
|
||||
"type_info": "Float4"
|
||||
},
|
||||
{
|
||||
"ordinal": 19,
|
||||
"ordinal": 20,
|
||||
"name": "created_at",
|
||||
"type_info": "Timestamp"
|
||||
},
|
||||
{
|
||||
"ordinal": 20,
|
||||
"ordinal": 21,
|
||||
"name": "updated_at",
|
||||
"type_info": "Timestamp"
|
||||
},
|
||||
{
|
||||
"ordinal": 21,
|
||||
"ordinal": 22,
|
||||
"name": "primary_track",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 22,
|
||||
"ordinal": 23,
|
||||
"name": "downloaded_at",
|
||||
"type_info": "Timestamp"
|
||||
},
|
||||
{
|
||||
"ordinal": 23,
|
||||
"ordinal": 24,
|
||||
"name": "last_streamed_at",
|
||||
"type_info": "Timestamp"
|
||||
},
|
||||
{
|
||||
"ordinal": 24,
|
||||
"ordinal": 25,
|
||||
"name": "n_streams",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 25,
|
||||
"ordinal": 26,
|
||||
"name": "artists: _",
|
||||
"type_info": "Jsonb"
|
||||
}
|
||||
|
@ -198,6 +203,7 @@
|
|||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
|
@ -217,5 +223,5 @@
|
|||
null
|
||||
]
|
||||
},
|
||||
"hash": "586725923e6284841ebeac8b2e9b74dc623f2eefe05adf9941ad64880317f360"
|
||||
"hash": "5471630b1ffc7f91c3c3d03b231a695cbf56bd8375aee419b1ff26033b98eae7"
|
||||
}
|
46
crates/db/.sqlx/query-b0c1e18c19c896a15971562dd66dfe718cf51c7d15540015076ca1c65ea9a631.json
generated
Normal file
46
crates/db/.sqlx/query-b0c1e18c19c896a15971562dd66dfe718cf51c7d15540015076ca1c65ea9a631.json
generated
Normal file
|
@ -0,0 +1,46 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "insert into tracks (src_id, service, name, duration, duration_ms,\n album_id, album_pos, ul_artists, isrc, description, file_size, track_gain, primary_track)\nvalues ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)\non conflict (src_id, service) do update set\n name = excluded.name,\n duration = case when tracks.duration_ms and not excluded.duration_ms\n then tracks.duration else coalesce(excluded.duration, tracks.duration) end,\n duration_ms = excluded.duration_ms or tracks.duration_ms,\n album_id = excluded.album_id,\n album_pos = coalesce(excluded.album_pos, tracks.album_pos),\n ul_artists = coalesce(excluded.ul_artists, tracks.ul_artists),\n isrc = coalesce(excluded.isrc, tracks.isrc),\n description = coalesce(excluded.description, tracks.description),\n file_size = coalesce(excluded.file_size, tracks.file_size),\n track_gain = coalesce(excluded.track_gain, tracks.track_gain),\n primary_track = coalesce(excluded.primary_track, tracks.primary_track)\nreturning id",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
{
|
||||
"Custom": {
|
||||
"name": "music_service",
|
||||
"kind": {
|
||||
"Enum": [
|
||||
"ty",
|
||||
"yt",
|
||||
"sp",
|
||||
"mx"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"Text",
|
||||
"Int4",
|
||||
"Bool",
|
||||
"Int4",
|
||||
"Int2",
|
||||
"TextArray",
|
||||
"Varchar",
|
||||
"Text",
|
||||
"Int8",
|
||||
"Float4",
|
||||
"Bool"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "b0c1e18c19c896a15971562dd66dfe718cf51c7d15540015076ca1c65ea9a631"
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "select t.id, t.src_id, t.service as \"service: _\", t.name, t.duration,\n b.id as album_id, b.src_id as album_src_id, b.service as \"album_service: _\", b.name as album_name, b.release_date,\n b.album_type as \"album_type: _\", b.image_url, b.image_date,\n t.album_pos, t.ul_artists, t.isrc, t.description, t.file_size, t.track_gain, t.created_at, t.updated_at,\n t.primary_track, t.downloaded_at, t.last_streamed_at, t.n_streams,\n jsonb_agg(json_build_object('id', a.src_id, 'sv', a.service, 'n', a.name) order by art.seq)\n filter (where a.src_id is not null) as \"artists: _\"\nfrom tracks t\n left join artists_tracks art on art.track_id = t.id\n left join artists a on a.id = art.artist_id\n left join albums b on b.id = t.album_id\nwhere t.id=$1\ngroup by (t.id, b.id)",
|
||||
"query": "select t.id, t.src_id, t.service as \"service: _\", t.name, t.duration, t.duration_ms,\n b.id as album_id, b.src_id as album_src_id, b.service as \"album_service: _\", b.name as album_name, b.release_date,\n b.album_type as \"album_type: _\", b.image_url, b.image_date,\n t.album_pos, t.ul_artists, t.isrc, t.description, t.file_size, t.track_gain, t.created_at, t.updated_at,\n t.primary_track, t.downloaded_at, t.last_streamed_at, t.n_streams,\n jsonb_agg(json_build_object('id', a.src_id, 'sv', a.service, 'n', a.name) order by art.seq)\n filter (where a.src_id is not null) as \"artists: _\"\nfrom tracks t\n left join artists_tracks art on art.track_id = t.id\n left join artists a on a.id = art.artist_id\n left join albums b on b.id = t.album_id\nwhere t.id=$1\ngroup by (t.id, b.id)",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
|
@ -42,16 +42,21 @@
|
|||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "duration_ms",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "album_id",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"ordinal": 7,
|
||||
"name": "album_src_id",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"ordinal": 8,
|
||||
"name": "album_service: _",
|
||||
"type_info": {
|
||||
"Custom": {
|
||||
|
@ -68,17 +73,17 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"ordinal": 9,
|
||||
"name": "album_name",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"ordinal": 10,
|
||||
"name": "release_date",
|
||||
"type_info": "Date"
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"ordinal": 11,
|
||||
"name": "album_type: _",
|
||||
"type_info": {
|
||||
"Custom": {
|
||||
|
@ -95,77 +100,77 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"ordinal": 12,
|
||||
"name": "image_url",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"ordinal": 13,
|
||||
"name": "image_date",
|
||||
"type_info": "Timestamp"
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"ordinal": 14,
|
||||
"name": "album_pos",
|
||||
"type_info": "Int2"
|
||||
},
|
||||
{
|
||||
"ordinal": 14,
|
||||
"ordinal": 15,
|
||||
"name": "ul_artists",
|
||||
"type_info": "TextArray"
|
||||
},
|
||||
{
|
||||
"ordinal": 15,
|
||||
"ordinal": 16,
|
||||
"name": "isrc",
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"ordinal": 16,
|
||||
"ordinal": 17,
|
||||
"name": "description",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 17,
|
||||
"ordinal": 18,
|
||||
"name": "file_size",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 18,
|
||||
"ordinal": 19,
|
||||
"name": "track_gain",
|
||||
"type_info": "Float4"
|
||||
},
|
||||
{
|
||||
"ordinal": 19,
|
||||
"ordinal": 20,
|
||||
"name": "created_at",
|
||||
"type_info": "Timestamp"
|
||||
},
|
||||
{
|
||||
"ordinal": 20,
|
||||
"ordinal": 21,
|
||||
"name": "updated_at",
|
||||
"type_info": "Timestamp"
|
||||
},
|
||||
{
|
||||
"ordinal": 21,
|
||||
"ordinal": 22,
|
||||
"name": "primary_track",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 22,
|
||||
"ordinal": 23,
|
||||
"name": "downloaded_at",
|
||||
"type_info": "Timestamp"
|
||||
},
|
||||
{
|
||||
"ordinal": 23,
|
||||
"ordinal": 24,
|
||||
"name": "last_streamed_at",
|
||||
"type_info": "Timestamp"
|
||||
},
|
||||
{
|
||||
"ordinal": 24,
|
||||
"ordinal": 25,
|
||||
"name": "n_streams",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 25,
|
||||
"ordinal": 26,
|
||||
"name": "artists: _",
|
||||
"type_info": "Jsonb"
|
||||
}
|
||||
|
@ -185,6 +190,7 @@
|
|||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
|
@ -204,5 +210,5 @@
|
|||
null
|
||||
]
|
||||
},
|
||||
"hash": "a5cdaf05833b1bad4b90b6960937795f508f338f088c3570e908f7c2df8c602b"
|
||||
"hash": "c51949fcbd59f9343c883e993cd7bcacffe3e5f5dd32e7ba9075819cceccfb0b"
|
||||
}
|
|
@ -30,6 +30,7 @@ CREATE TABLE
|
|||
service public.music_service NOT NULL,
|
||||
NAME TEXT NOT NULL,
|
||||
duration INTEGER,
|
||||
duration_ms bool NOT NULL DEFAULT FALSE,
|
||||
album_pos SMALLINT,
|
||||
album_id INTEGER NOT NULL,
|
||||
ul_artists TEXT[],
|
||||
|
@ -54,7 +55,9 @@ COMMENT ON COLUMN public.tracks.service IS E'Service providing the track';
|
|||
|
||||
COMMENT ON COLUMN public.tracks.name IS E'Track name';
|
||||
|
||||
COMMENT ON COLUMN public.tracks.duration IS E'Duration of the track in seconds';
|
||||
COMMENT ON COLUMN public.tracks.duration IS E'Duration of the track in milliseconds';
|
||||
|
||||
COMMENT ON COLUMN public.tracks.duration_ms IS E'True if the duration is in millisecond precision';
|
||||
|
||||
COMMENT ON COLUMN public.tracks.file_size IS E'File size in bytes';
|
||||
|
||||
|
@ -548,22 +551,27 @@ $$;
|
|||
|
||||
ALTER FUNCTION public.set_header_image_date () OWNER TO postgres;
|
||||
|
||||
CREATE TRIGGER artists_set_image_date BEFORE
|
||||
INSERT OR UPDATE OF image_url ON public.artists FOR EACH ROW
|
||||
CREATE TRIGGER artists_set_image_date BEFORE INSERT
|
||||
OR
|
||||
UPDATE OF image_url ON public.artists FOR EACH ROW
|
||||
EXECUTE PROCEDURE public.set_image_date ();
|
||||
|
||||
CREATE TRIGGER artists_set_header_image_date BEFORE
|
||||
INSERT OR UPDATE OF header_image_url ON public.artists FOR EACH ROW
|
||||
CREATE TRIGGER artists_set_header_image_date BEFORE INSERT
|
||||
OR
|
||||
UPDATE OF header_image_url ON public.artists FOR EACH ROW
|
||||
EXECUTE PROCEDURE public.set_header_image_date ();
|
||||
|
||||
CREATE TRIGGER albums_set_image_date BEFORE
|
||||
INSERT OR UPDATE OF image_url ON public.albums FOR EACH ROW
|
||||
CREATE TRIGGER albums_set_image_date BEFORE INSERT
|
||||
OR
|
||||
UPDATE OF image_url ON public.albums FOR EACH ROW
|
||||
EXECUTE PROCEDURE public.set_image_date ();
|
||||
|
||||
CREATE TRIGGER playlists_set_image_date BEFORE
|
||||
INSERT OR UPDATE OF image_url ON public.playlists FOR EACH ROW
|
||||
CREATE TRIGGER playlists_set_image_date BEFORE INSERT
|
||||
OR
|
||||
UPDATE OF image_url ON public.playlists FOR EACH ROW
|
||||
EXECUTE PROCEDURE public.set_image_date ();
|
||||
|
||||
CREATE TRIGGER users_set_image_date BEFORE
|
||||
INSERT OR UPDATE OF image_url ON public.users FOR EACH ROW
|
||||
CREATE TRIGGER users_set_image_date BEFORE INSERT
|
||||
OR
|
||||
UPDATE OF image_url ON public.users FOR EACH ROW
|
||||
EXECUTE PROCEDURE public.set_image_date ();
|
||||
|
|
|
@ -8,6 +8,7 @@ Track(
|
|||
service: yt,
|
||||
name: "empty",
|
||||
duration: None,
|
||||
duration_ms: false,
|
||||
artists: [],
|
||||
album_id: 1,
|
||||
album: AlbumTag(
|
||||
|
|
|
@ -7,7 +7,8 @@ Track(
|
|||
src_id: "g0iRiJ_ck48",
|
||||
service: yt,
|
||||
name: "Aulë und Yavanna",
|
||||
duration: Some(216),
|
||||
duration: Some(216481),
|
||||
duration_ms: true,
|
||||
artists: [
|
||||
ArtistTag(
|
||||
id: Some("yt:UC_MxOdawj_BStPs4CKBYD0Q"),
|
||||
|
|
|
@ -6,7 +6,7 @@ TrackSlim(
|
|||
src_id: "g0iRiJ_ck48",
|
||||
service: yt,
|
||||
name: "Aulë und Yavanna",
|
||||
duration: Some(216),
|
||||
duration: Some(216481),
|
||||
artists: [
|
||||
ArtistTag(
|
||||
id: Some("yt:UC_MxOdawj_BStPs4CKBYD0Q"),
|
||||
|
|
|
@ -19,6 +19,7 @@ pub struct Track {
|
|||
pub service: MusicService,
|
||||
pub name: String,
|
||||
pub duration: Option<i32>,
|
||||
pub duration_ms: bool,
|
||||
pub artists: Vec<ArtistTag>,
|
||||
pub album_id: i32,
|
||||
pub album: AlbumTag,
|
||||
|
@ -42,6 +43,7 @@ struct TrackRow {
|
|||
service: MusicService,
|
||||
name: String,
|
||||
duration: Option<i32>,
|
||||
duration_ms: bool,
|
||||
album_id: i32,
|
||||
album_src_id: String,
|
||||
album_service: MusicService,
|
||||
|
@ -72,6 +74,7 @@ pub struct TrackNew<'a> {
|
|||
pub service: MusicService,
|
||||
pub name: &'a str,
|
||||
pub duration: Option<i32>,
|
||||
pub duration_ms: bool,
|
||||
pub album_id: i32,
|
||||
pub album_pos: Option<i16>,
|
||||
pub ul_artists: Option<&'a [String]>,
|
||||
|
@ -87,6 +90,7 @@ pub struct TrackNew<'a> {
|
|||
pub struct TrackUpdate<'a> {
|
||||
pub name: Option<&'a str>,
|
||||
pub duration: Option<Option<i32>>,
|
||||
pub duration_ms: Option<bool>,
|
||||
pub album_id: Option<i32>,
|
||||
pub album_pos: Option<Option<i16>>,
|
||||
pub ul_artists: Option<&'a [String]>,
|
||||
|
@ -188,7 +192,7 @@ impl Track {
|
|||
Id::Db(id) => {
|
||||
sqlx::query_as!(
|
||||
TrackRow,
|
||||
r#"select t.id, t.src_id, t.service as "service: _", t.name, t.duration,
|
||||
r#"select t.id, t.src_id, t.service as "service: _", t.name, t.duration, t.duration_ms,
|
||||
b.id as album_id, b.src_id as album_src_id, b.service as "album_service: _", b.name as album_name, b.release_date,
|
||||
b.album_type as "album_type: _", b.image_url, b.image_date,
|
||||
t.album_pos, t.ul_artists, t.isrc, t.description, t.file_size, t.track_gain, t.created_at, t.updated_at,
|
||||
|
@ -209,7 +213,7 @@ group by (t.id, b.id)"#,
|
|||
Id::Src(src_id, srv) => {
|
||||
let res = sqlx::query_as!(
|
||||
TrackRow,
|
||||
r#"select t.id, t.src_id, t.service as "service: _", t.name, t.duration,
|
||||
r#"select t.id, t.src_id, t.service as "service: _", t.name, t.duration, t.duration_ms,
|
||||
b.id as album_id, b.src_id as album_src_id, b.service as "album_service: _", b.name as album_name, b.release_date,
|
||||
b.album_type as "album_type: _", b.image_url, b.image_date,
|
||||
t.album_pos, t.ul_artists, t.isrc, t.description, t.file_size, t.track_gain, t.created_at, t.updated_at,
|
||||
|
@ -234,7 +238,7 @@ group by (t.id, b.id)"#,
|
|||
None => {
|
||||
sqlx::query_as!(
|
||||
TrackRow,
|
||||
r#"select t.id, t.src_id, t.service as "service: _", t.name, t.duration,
|
||||
r#"select t.id, t.src_id, t.service as "service: _", t.name, t.duration, t.duration_ms,
|
||||
b.id as album_id, b.src_id as album_src_id, b.service as "album_service: _", b.name as album_name, b.release_date,
|
||||
b.album_type as "album_type: _", b.image_url, b.image_date,
|
||||
t.album_pos, t.ul_artists, t.isrc, t.description, t.file_size, t.track_gain, t.created_at, t.updated_at,
|
||||
|
@ -409,12 +413,14 @@ impl TrackNew<'_> {
|
|||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||
{
|
||||
let res = sqlx::query!(
|
||||
r#"insert into tracks (src_id, service, name, duration,
|
||||
r#"insert into tracks (src_id, service, name, duration, duration_ms,
|
||||
album_id, album_pos, ul_artists, isrc, description, file_size, track_gain, primary_track)
|
||||
values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
|
||||
values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)
|
||||
on conflict (src_id, service) do update set
|
||||
name = excluded.name,
|
||||
duration = coalesce(excluded.duration, tracks.duration),
|
||||
duration = case when tracks.duration_ms and not excluded.duration_ms
|
||||
then tracks.duration else coalesce(excluded.duration, tracks.duration) end,
|
||||
duration_ms = excluded.duration_ms or tracks.duration_ms,
|
||||
album_id = excluded.album_id,
|
||||
album_pos = coalesce(excluded.album_pos, tracks.album_pos),
|
||||
ul_artists = coalesce(excluded.ul_artists, tracks.ul_artists),
|
||||
|
@ -428,6 +434,7 @@ returning id"#,
|
|||
self.service as MusicService,
|
||||
self.name,
|
||||
self.duration,
|
||||
self.duration_ms,
|
||||
self.album_id,
|
||||
self.album_pos,
|
||||
self.ul_artists.as_deref(),
|
||||
|
@ -464,6 +471,14 @@ impl TrackUpdate<'_> {
|
|||
query.push_bind(duration);
|
||||
n += 1;
|
||||
}
|
||||
if let Some(duration_ms) = &self.duration_ms {
|
||||
if n != 0 {
|
||||
query.push(", ");
|
||||
}
|
||||
query.push("duration_ms=");
|
||||
query.push_bind(duration_ms);
|
||||
n += 1;
|
||||
}
|
||||
if let Some(album_id) = &self.album_id {
|
||||
if n != 0 {
|
||||
query.push(", ");
|
||||
|
@ -678,6 +693,7 @@ impl From<TrackRow> for Track {
|
|||
service: value.service,
|
||||
name: value.name,
|
||||
duration: value.duration,
|
||||
duration_ms: value.duration_ms,
|
||||
artists: util::map_artists(value.artists, value.ul_artists),
|
||||
album_id: value.album_id,
|
||||
album: AlbumTag {
|
||||
|
@ -731,7 +747,7 @@ impl From<Track> for tiraya_api_model::Track {
|
|||
Self {
|
||||
id: tiraya_api_model::TId::new(t.src_id, t.service.into()),
|
||||
name: t.name,
|
||||
duration: t.duration,
|
||||
duration: t.duration.map(|d| d / 1000),
|
||||
artists: t
|
||||
.artists
|
||||
.into_iter()
|
||||
|
@ -755,7 +771,7 @@ impl From<TrackSlim> for tiraya_api_model::TrackSlim {
|
|||
Self {
|
||||
id: tiraya_api_model::TId::new(t.src_id, t.service.into()),
|
||||
name: t.name,
|
||||
duration: t.duration,
|
||||
duration: t.duration.map(|d| d / 1000),
|
||||
artists: t
|
||||
.artists
|
||||
.into_iter()
|
||||
|
@ -785,7 +801,8 @@ mod tests {
|
|||
src_id: "g0iRiJ_ck48",
|
||||
service: MusicService::YouTube,
|
||||
name: "Aulë und Yavanna",
|
||||
duration: Some(216),
|
||||
duration: Some(216481),
|
||||
duration_ms: true,
|
||||
album_id: 1,
|
||||
album_pos: Some(1),
|
||||
ul_artists: Some(&ul_artists),
|
||||
|
@ -837,6 +854,7 @@ mod tests {
|
|||
let clear = TrackUpdate {
|
||||
name: Some("empty"),
|
||||
duration: Some(None),
|
||||
duration_ms: Some(false),
|
||||
album_id: None,
|
||||
album_pos: Some(None),
|
||||
ul_artists: Some(&[]),
|
||||
|
|
|
@ -850,7 +850,7 @@ impl YouTubeExtractor {
|
|||
src_id: &track.id,
|
||||
service: MusicService::YouTube,
|
||||
name: &track.name,
|
||||
duration: track.duration.and_then(|v| v.try_into().ok()),
|
||||
duration: track.duration.and_then(|d| (d * 1000).try_into().ok()),
|
||||
album_id,
|
||||
album_pos: track.track_nr.and_then(|v| v.try_into().ok()),
|
||||
ul_artists: ul_artists.as_deref(),
|
||||
|
@ -1346,7 +1346,8 @@ mod tests {
|
|||
src_id: "voLnMeuXQo4",
|
||||
service: yt,
|
||||
name: "PTT (Paint the Town)",
|
||||
duration: Some(202),
|
||||
duration: Some(202000),
|
||||
duration_ms: false,
|
||||
artists: [
|
||||
ArtistTag(
|
||||
id: Some("yt:UCa4ZqZPRjz9MYYnfpoh2few"),
|
||||
|
|
|
@ -7,7 +7,7 @@ expression: tracks
|
|||
src_id: "QapQgsYqR0o",
|
||||
service: yt,
|
||||
name: "747",
|
||||
duration: Some(144),
|
||||
duration: Some(144000),
|
||||
artists: [
|
||||
ArtistTag(
|
||||
id: Some("yt:UCFTcSVPYRWlDoHisR-ZKwgw"),
|
||||
|
@ -29,7 +29,7 @@ expression: tracks
|
|||
src_id: "gNd1pbc0suY",
|
||||
service: yt,
|
||||
name: "Süchtig",
|
||||
duration: Some(192),
|
||||
duration: Some(192000),
|
||||
artists: [
|
||||
ArtistTag(
|
||||
id: Some("yt:UCFTcSVPYRWlDoHisR-ZKwgw"),
|
||||
|
@ -51,7 +51,7 @@ expression: tracks
|
|||
src_id: "DIhEDU66xh8",
|
||||
service: yt,
|
||||
name: "Happy End (feat. Sido)",
|
||||
duration: Some(138),
|
||||
duration: Some(138000),
|
||||
artists: [
|
||||
ArtistTag(
|
||||
id: Some("yt:UCFTcSVPYRWlDoHisR-ZKwgw"),
|
||||
|
@ -73,7 +73,7 @@ expression: tracks
|
|||
src_id: "oQI0IT2cEfw",
|
||||
service: yt,
|
||||
name: "VIBE",
|
||||
duration: Some(157),
|
||||
duration: Some(157000),
|
||||
artists: [
|
||||
ArtistTag(
|
||||
id: Some("yt:UCFTcSVPYRWlDoHisR-ZKwgw"),
|
||||
|
@ -95,7 +95,7 @@ expression: tracks
|
|||
src_id: "C_pZsCHUqUU",
|
||||
service: yt,
|
||||
name: "Melatonin",
|
||||
duration: Some(140),
|
||||
duration: Some(140000),
|
||||
artists: [
|
||||
ArtistTag(
|
||||
id: Some("yt:UCFTcSVPYRWlDoHisR-ZKwgw"),
|
||||
|
@ -121,7 +121,7 @@ expression: tracks
|
|||
src_id: "pjCRr1zHpLs",
|
||||
service: yt,
|
||||
name: "Zehenspitzen",
|
||||
duration: Some(175),
|
||||
duration: Some(175000),
|
||||
artists: [
|
||||
ArtistTag(
|
||||
id: Some("yt:UCFTcSVPYRWlDoHisR-ZKwgw"),
|
||||
|
@ -143,7 +143,7 @@ expression: tracks
|
|||
src_id: "1xwJWeWdpHw",
|
||||
service: yt,
|
||||
name: "Summer Nights",
|
||||
duration: Some(178),
|
||||
duration: Some(178000),
|
||||
artists: [
|
||||
ArtistTag(
|
||||
id: Some("yt:UCFTcSVPYRWlDoHisR-ZKwgw"),
|
||||
|
@ -165,7 +165,7 @@ expression: tracks
|
|||
src_id: "2GY7M08AtM4",
|
||||
service: yt,
|
||||
name: "Schwarze Herzen",
|
||||
duration: Some(145),
|
||||
duration: Some(145000),
|
||||
artists: [
|
||||
ArtistTag(
|
||||
id: Some("yt:UCFTcSVPYRWlDoHisR-ZKwgw"),
|
||||
|
@ -191,7 +191,7 @@ expression: tracks
|
|||
src_id: "hw1bZ0unqgg",
|
||||
service: yt,
|
||||
name: "Stadtbezirk",
|
||||
duration: Some(170),
|
||||
duration: Some(170000),
|
||||
artists: [
|
||||
ArtistTag(
|
||||
id: Some("yt:UCFTcSVPYRWlDoHisR-ZKwgw"),
|
||||
|
@ -213,7 +213,7 @@ expression: tracks
|
|||
src_id: "c882l-0i1Ds",
|
||||
service: yt,
|
||||
name: "No Hard Feelings",
|
||||
duration: Some(159),
|
||||
duration: Some(159000),
|
||||
artists: [
|
||||
ArtistTag(
|
||||
id: Some("yt:UCFTcSVPYRWlDoHisR-ZKwgw"),
|
||||
|
@ -235,7 +235,7 @@ expression: tracks
|
|||
src_id: "5WRcgP_WDbY",
|
||||
service: yt,
|
||||
name: "Bitte Geh Nicht",
|
||||
duration: Some(132),
|
||||
duration: Some(132000),
|
||||
artists: [
|
||||
ArtistTag(
|
||||
id: Some("yt:UCFTcSVPYRWlDoHisR-ZKwgw"),
|
||||
|
@ -257,7 +257,7 @@ expression: tracks
|
|||
src_id: "bdXaTyLRhtQ",
|
||||
service: yt,
|
||||
name: "Als ob du mich liebst (feat. Vanessa Mai)",
|
||||
duration: Some(142),
|
||||
duration: Some(142000),
|
||||
artists: [
|
||||
ArtistTag(
|
||||
id: Some("yt:UCymtzNvoLbYPhnZbd2CcCZA"),
|
||||
|
@ -279,7 +279,7 @@ expression: tracks
|
|||
src_id: "u1xwYB0ViHQ",
|
||||
service: yt,
|
||||
name: "Aus & Vorbei",
|
||||
duration: Some(135),
|
||||
duration: Some(135000),
|
||||
artists: [
|
||||
ArtistTag(
|
||||
id: Some("yt:UCFTcSVPYRWlDoHisR-ZKwgw"),
|
||||
|
@ -301,7 +301,7 @@ expression: tracks
|
|||
src_id: "jI9nQeKGf4E",
|
||||
service: yt,
|
||||
name: "Unendlich",
|
||||
duration: Some(169),
|
||||
duration: Some(169000),
|
||||
artists: [
|
||||
ArtistTag(
|
||||
id: Some("yt:UCpuUi6e7YMwhSg8Q6erovXg"),
|
||||
|
|
|
@ -7,7 +7,7 @@ expression: album_tracks
|
|||
src_id: "BGcUVJXViqQ",
|
||||
service: yt,
|
||||
name: "고블린 Goblin",
|
||||
duration: Some(194),
|
||||
duration: Some(194000),
|
||||
artists: [
|
||||
ArtistTag(
|
||||
id: Some("yt:UCfwCE5VhPMGxNPFxtVv7lRw"),
|
||||
|
@ -29,7 +29,7 @@ expression: album_tracks
|
|||
src_id: "7_Bav4c7UGM",
|
||||
service: yt,
|
||||
name: "온더문 On The Moon",
|
||||
duration: Some(256),
|
||||
duration: Some(256000),
|
||||
artists: [
|
||||
ArtistTag(
|
||||
id: Some("yt:UCfwCE5VhPMGxNPFxtVv7lRw"),
|
||||
|
@ -51,7 +51,7 @@ expression: album_tracks
|
|||
src_id: "kzUZABVj5UQ",
|
||||
service: yt,
|
||||
name: "도로시 Dorothy",
|
||||
duration: Some(241),
|
||||
duration: Some(241000),
|
||||
artists: [
|
||||
ArtistTag(
|
||||
id: Some("yt:UCfwCE5VhPMGxNPFxtVv7lRw"),
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -7,7 +7,8 @@ Track(
|
|||
src_id: "BL-aIpCLWnU",
|
||||
service: yt,
|
||||
name: "Black Mamba",
|
||||
duration: Some(175),
|
||||
duration: Some(175000),
|
||||
duration_ms: false,
|
||||
artists: [
|
||||
ArtistTag(
|
||||
id: Some("yt:UCEdZAdnnKqbaHOlv8nM6OtA"),
|
||||
|
|
|
@ -8,7 +8,7 @@ expression: pl_entries
|
|||
src_id: "xwFRUfisow8",
|
||||
service: yt,
|
||||
name: "NXDE - (G)I-DLE Cover Español",
|
||||
duration: Some(180),
|
||||
duration: Some(180000),
|
||||
artists: [
|
||||
ArtistTag(
|
||||
id: Some("yt:UCTr0VOWDDZAR1ij4CjkD0VA"),
|
||||
|
@ -34,7 +34,7 @@ expression: pl_entries
|
|||
src_id: "9W6U3g2TecE",
|
||||
service: yt,
|
||||
name: "SHUT DOWN - BLACKPINK Cover Español",
|
||||
duration: Some(177),
|
||||
duration: Some(177000),
|
||||
artists: [
|
||||
ArtistTag(
|
||||
id: Some("yt:UCTr0VOWDDZAR1ij4CjkD0VA"),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue