Grouping and filtering music files
This commit is contained in:
@@ -1,11 +1,14 @@
|
||||
#![allow(clippy::missing_errors_doc)]
|
||||
#![allow(clippy::unnecessary_struct_initialization)]
|
||||
#![allow(clippy::unused_async)]
|
||||
use axum::{debug_handler, extract::Query};
|
||||
use loco_rs::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use axum::debug_handler;
|
||||
|
||||
use crate::models::_entities::music_files::{ActiveModel, Entity, Model};
|
||||
use crate::models::{
|
||||
_entities::music_files::{ActiveModel, Entity, Model},
|
||||
music_files::MusicFilter,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct Params {
|
||||
@@ -26,14 +29,25 @@ impl Params {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum GroupByColumn {
|
||||
Title,
|
||||
Artist,
|
||||
Album,
|
||||
}
|
||||
|
||||
async fn load_item(ctx: &AppContext, id: i32) -> Result<Model> {
|
||||
let item = Entity::find_by_id(id).one(&ctx.db).await?;
|
||||
item.ok_or_else(|| Error::NotFound)
|
||||
}
|
||||
|
||||
#[debug_handler]
|
||||
pub async fn list(State(ctx): State<AppContext>) -> Result<Response> {
|
||||
format::json(Entity::find().all(&ctx.db).await?)
|
||||
pub async fn list(
|
||||
State(ctx): State<AppContext>,
|
||||
Query(filter): Query<MusicFilter>,
|
||||
) -> Result<Response> {
|
||||
format::json(Model::filter_by(&ctx.db, filter).await?)
|
||||
}
|
||||
|
||||
#[debug_handler]
|
||||
@@ -70,6 +84,34 @@ pub async fn get_one(Path(id): Path<i32>, State(ctx): State<AppContext>) -> Resu
|
||||
format::json(load_item(&ctx, id).await?)
|
||||
}
|
||||
|
||||
pub async fn group_by(
|
||||
Path(column): Path<GroupByColumn>,
|
||||
State(ctx): State<AppContext>,
|
||||
Query(filter): Query<MusicFilter>,
|
||||
) -> Result<Response> {
|
||||
use GroupByColumn::*;
|
||||
|
||||
let rows = Model::filter_by(&ctx.db, filter).await?;
|
||||
|
||||
let mut grouped: std::collections::HashMap<String, Vec<Model>> =
|
||||
std::collections::HashMap::new();
|
||||
|
||||
for row in rows {
|
||||
let key = match column {
|
||||
Title => row.title.clone().unwrap_or_else(|| "<unknown>".to_string()),
|
||||
Artist => row
|
||||
.artist
|
||||
.clone()
|
||||
.unwrap_or_else(|| "<unknown>".to_string()),
|
||||
Album => row.album.clone().unwrap_or_else(|| "<unknown>".to_string()),
|
||||
};
|
||||
|
||||
grouped.entry(key).or_default().push(row);
|
||||
}
|
||||
|
||||
format::json(grouped)
|
||||
}
|
||||
|
||||
pub fn routes() -> Routes {
|
||||
Routes::new()
|
||||
.prefix("api/music_files/")
|
||||
@@ -79,4 +121,5 @@ pub fn routes() -> Routes {
|
||||
.add("{id}", delete(remove))
|
||||
.add("{id}", put(update))
|
||||
.add("{id}", patch(update))
|
||||
.add("/group_by/{column}", get(group_by))
|
||||
}
|
||||
|
@@ -1,7 +1,8 @@
|
||||
use loco_rs::prelude::*;
|
||||
use sea_orm::entity::prelude::*;
|
||||
use sea_orm::{entity::prelude::*, Condition};
|
||||
use serde::{Deserialize, Deserializer};
|
||||
|
||||
use crate::services::suggestion::MetadataFields;
|
||||
use crate::{models::_entities::music_files, services::suggestion::MetadataFields};
|
||||
|
||||
use lofty::{
|
||||
config::WriteOptions,
|
||||
@@ -15,6 +16,34 @@ use tracing::error;
|
||||
pub use super::_entities::music_files::{ActiveModel, Entity, Model};
|
||||
pub type MusicFiles = Entity;
|
||||
|
||||
pub enum MatchValue {
|
||||
Exact(String),
|
||||
Partial(String),
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for MatchValue {
|
||||
fn deserialize<D>(deserializer: D) -> Result<MatchValue, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let s = String::deserialize(deserializer)?;
|
||||
if let Some(stripped) = s.strip_prefix("exact:") {
|
||||
Ok(MatchValue::Exact(stripped.to_string()))
|
||||
} else if let Some(stripped) = s.strip_prefix("partial:") {
|
||||
Ok(MatchValue::Partial(stripped.to_string()))
|
||||
} else {
|
||||
Ok(MatchValue::Partial(s))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct MusicFilter {
|
||||
pub title: Option<MatchValue>,
|
||||
pub artist: Option<MatchValue>,
|
||||
pub album: Option<MatchValue>,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl ActiveModelBehavior for ActiveModel {
|
||||
async fn before_save<C>(self, _db: &C, insert: bool) -> std::result::Result<Self, DbErr>
|
||||
@@ -32,7 +61,35 @@ impl ActiveModelBehavior for ActiveModel {
|
||||
}
|
||||
|
||||
// implement your read-oriented logic here
|
||||
impl Model {}
|
||||
impl Model {
|
||||
pub async fn filter_by(db: &DatabaseConnection, filter: MusicFilter) -> ModelResult<Vec<Self>> {
|
||||
let mut query = Entity::find();
|
||||
let mut condition = Condition::all();
|
||||
|
||||
if let Some(title) = filter.title {
|
||||
condition = condition.add(match title {
|
||||
MatchValue::Exact(val) => music_files::Column::Title.eq(val),
|
||||
MatchValue::Partial(val) => music_files::Column::Title.contains(val),
|
||||
});
|
||||
}
|
||||
if let Some(artist) = filter.artist {
|
||||
condition = condition.add(match artist {
|
||||
MatchValue::Exact(val) => music_files::Column::Artist.eq(val),
|
||||
MatchValue::Partial(val) => music_files::Column::Artist.contains(val),
|
||||
});
|
||||
}
|
||||
if let Some(album) = filter.album {
|
||||
condition = condition.add(match album {
|
||||
MatchValue::Exact(val) => music_files::Column::Album.eq(val),
|
||||
MatchValue::Partial(val) => music_files::Column::Album.contains(val),
|
||||
});
|
||||
}
|
||||
|
||||
query = query.filter(condition);
|
||||
|
||||
query.all(db).await.map_err(|_| ModelError::EntityNotFound)
|
||||
}
|
||||
}
|
||||
|
||||
// implement your write-oriented logic here
|
||||
impl ActiveModel {}
|
||||
|
Reference in New Issue
Block a user