refactor: organize models and library, include detailed types in track JSON representation

- add basic searching query generators to `Artist`, `Collection`, `Track`
- add detailed variants of models
- split websocket message types to input/output-specific messages to constrain lifetimes
- organize imports
This commit is contained in:
skybldev
2026-10-31 01:46:07 -04:00
parent 5836f671d6
commit 57d70e773f
4 changed files with 233 additions and 75 deletions

View File

@@ -1,7 +1,13 @@
use diesel::prelude::*;
use diesel::dsl::{AsSelect, Filter, Select, Eq, auto_type};
use diesel::sqlite::Sqlite;
use futures::task::waker;
use serde::{Deserialize, Serialize};
use crate::schema::{artists, collections, tracks};
use crate::library::Library;
type All<S, M> = Select<S, AsSelect<M, Sqlite>>;
#[derive(Debug, Clone, Queryable, Selectable, Identifiable)]
#[diesel(table_name = artists)]
@@ -12,6 +18,26 @@ pub struct Artist {
pub display_name: String,
}
impl Artist {
pub fn all() -> All<artists::table, Self> {
artists::table.select(Self::as_select())
}
#[auto_type(no_type_alias)]
pub fn by_id(id: i32) -> _ {
let all: All<artists::table, Self> = Self::all();
let filter = artists::id.eq(id);
all.filter(filter)
}
#[auto_type(no_type_alias)]
pub fn by_original_name(name: &str) -> _ {
let all: All<artists::table, Self> = Self::all();
let filter = artists::original_name.eq(name);
all.filter(filter)
}
}
#[derive(Debug, Clone, Queryable, Selectable, Identifiable)]
#[diesel(table_name = collections)]
#[diesel(check_for_backend(diesel::sqlite::Sqlite))]
@@ -23,7 +49,26 @@ pub struct Collection {
pub display_name: String,
}
// TODO: make this more ergonomic (nested artist object instead of artist ID, etc)
impl Collection {
pub fn all() -> All<collections::table, Self> {
collections::table.select(Self::as_select())
}
#[auto_type(no_type_alias)]
pub fn by_id(id: i32) -> _ {
let all: All<collections::table, Self> = Self::all();
let filter = collections::id.eq(id);
all.filter(filter)
}
#[auto_type(no_type_alias)]
pub fn by_original_name(name: &str) -> _ {
let all: All<collections::table, Self> = Self::all();
let filter = collections::original_name.eq(name);
all.filter(filter)
}
}
#[derive(
Debug, Clone, Queryable, Selectable, Identifiable, Associations, Serialize, Deserialize,
)]
@@ -44,6 +89,58 @@ pub struct Track {
pub length_sec: i32,
}
impl Track {
pub fn all() -> All<tracks::table, Self> {
tracks::table.select(Self::as_select())
}
#[auto_type(no_type_alias)]
pub fn by_id(id: i32) -> _ {
let all: All<tracks::table, Self> = Self::all();
let filter = tracks::id.eq(id);
all.filter(filter)
}
#[auto_type(no_type_alias)]
pub fn by_original_title(title: &str) -> _ {
let all: All<tracks::table, Self> = Self::all();
let filter = tracks::original_title.eq(title);
all.filter(filter)
}
}
#[derive(Debug, Serialize)]
pub struct DetailedArtist {
pub id: i32,
pub original_name: String,
pub display_name: String,
}
#[derive(Debug, Serialize)]
pub struct DetailedCollection<'a> {
pub id: i32,
pub artist: Option<&'a DetailedArtist>,
pub collection_type: String,
pub original_name: String,
pub display_name: String,
}
#[derive(Debug, Serialize)]
pub struct DetailedTrack<'a> {
pub id: i32,
pub file_path: String,
pub artist: Option<&'a DetailedArtist>,
// a collection could be under another artist (e.g. mixtapes)
pub collection: Option<&'a DetailedCollection<'a>>,
pub original_title: Option<String>,
pub display_title: Option<String>,
pub track_number: Option<i32>,
pub genre: Option<String>,
pub track_datetime: Option<String>,
pub length_sec: i32,
}
#[derive(Debug, Clone, Insertable)]
#[diesel(table_name = artists)]
pub struct NewArtist<'a> {