refactor: app -> rave; and some other poking

This commit is contained in:
Lys 2023-10-09 22:24:32 +03:00
parent 7198eda4ee
commit b18d0dd747
Signed by: lyssieth
GPG key ID: C9CF3D614FAA3940
29 changed files with 453 additions and 51 deletions

View file

@ -1,5 +1,5 @@
[workspace] [workspace]
members = ["app", "entities", "migration"] members = ["rave", "entities", "migration"]
resolver = "2" resolver = "2"
[workspace.dependencies] [workspace.dependencies]

View file

@ -1,30 +0,0 @@
use crate::{
authentication::Authentication,
subsonic::SubsonicResponse,
utils::{self},
};
use poem::web::{Data, Query};
use poem_ext::db::DbTxn;
use serde::Deserialize;
#[poem::handler]
pub async fn get_album(
Data(txn): Data<&DbTxn>,
auth: Authentication,
Query(params): Query<GetAlbumParams>,
) -> SubsonicResponse {
let u = utils::verify_user(txn.clone(), auth).await;
match u {
Ok(_) => {}
Err(e) => return e,
}
todo!("get_album not implemented");
}
#[derive(Debug, Clone, Deserialize)]
pub struct GetAlbumParams {
pub id: i32,
}

View file

@ -9,9 +9,8 @@ pub struct Model {
#[sea_orm(primary_key)] #[sea_orm(primary_key)]
pub id: i64, pub id: i64,
pub name: String, pub name: String,
pub artist: Option<String>,
pub artist_id: Option<i64>, pub artist_id: Option<i64>,
pub cover_art: Option<String>, pub cover_art_id: Option<i64>,
pub song_count: i32, pub song_count: i32,
pub duration: i64, pub duration: i64,
pub play_count: i64, pub play_count: i64,
@ -32,6 +31,14 @@ pub enum Relation {
on_delete = "SetNull" on_delete = "SetNull"
)] )]
Artist, Artist,
#[sea_orm(
belongs_to = "super::cover_art::Entity",
from = "Column::CoverArtId",
to = "super::cover_art::Column::Id",
on_update = "NoAction",
on_delete = "SetNull"
)]
CoverArt,
#[sea_orm( #[sea_orm(
belongs_to = "super::music_folder::Entity", belongs_to = "super::music_folder::Entity",
from = "Column::MusicFolderId", from = "Column::MusicFolderId",
@ -40,6 +47,8 @@ pub enum Relation {
on_delete = "Cascade" on_delete = "Cascade"
)] )]
MusicFolder, MusicFolder,
#[sea_orm(has_many = "super::track::Entity")]
Track,
} }
impl Related<super::artist::Entity> for Entity { impl Related<super::artist::Entity> for Entity {
@ -48,10 +57,22 @@ impl Related<super::artist::Entity> for Entity {
} }
} }
impl Related<super::cover_art::Entity> for Entity {
fn to() -> RelationDef {
Relation::CoverArt.def()
}
}
impl Related<super::music_folder::Entity> for Entity { impl Related<super::music_folder::Entity> for Entity {
fn to() -> RelationDef { fn to() -> RelationDef {
Relation::MusicFolder.def() Relation::MusicFolder.def()
} }
} }
impl Related<super::track::Entity> for Entity {
fn to() -> RelationDef {
Relation::Track.def()
}
}
impl ActiveModelBehavior for ActiveModel {} impl ActiveModelBehavior for ActiveModel {}

View file

@ -10,7 +10,7 @@ pub struct Model {
pub id: i64, pub id: i64,
#[sea_orm(unique)] #[sea_orm(unique)]
pub name: String, pub name: String,
pub cover_art: Option<String>, pub cover_art_id: Option<i64>,
pub artist_image_url: Option<String>, pub artist_image_url: Option<String>,
pub album_count: i32, pub album_count: i32,
pub starred: bool, pub starred: bool,
@ -20,6 +20,16 @@ pub struct Model {
pub enum Relation { pub enum Relation {
#[sea_orm(has_many = "super::album::Entity")] #[sea_orm(has_many = "super::album::Entity")]
Album, Album,
#[sea_orm(
belongs_to = "super::cover_art::Entity",
from = "Column::CoverArtId",
to = "super::cover_art::Column::Id",
on_update = "NoAction",
on_delete = "SetNull"
)]
CoverArt,
#[sea_orm(has_many = "super::track::Entity")]
Track,
} }
impl Related<super::album::Entity> for Entity { impl Related<super::album::Entity> for Entity {
@ -28,4 +38,16 @@ impl Related<super::album::Entity> for Entity {
} }
} }
impl Related<super::cover_art::Entity> for Entity {
fn to() -> RelationDef {
Relation::CoverArt.def()
}
}
impl Related<super::track::Entity> for Entity {
fn to() -> RelationDef {
Relation::Track.def()
}
}
impl ActiveModelBehavior for ActiveModel {} impl ActiveModelBehavior for ActiveModel {}

42
entities/src/cover_art.rs Normal file
View file

@ -0,0 +1,42 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.3
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
#[sea_orm(table_name = "cover_art")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
pub path: String,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(has_many = "super::album::Entity")]
Album,
#[sea_orm(has_many = "super::artist::Entity")]
Artist,
#[sea_orm(has_many = "super::track::Entity")]
Track,
}
impl Related<super::album::Entity> for Entity {
fn to() -> RelationDef {
Relation::Album.def()
}
}
impl Related<super::artist::Entity> for Entity {
fn to() -> RelationDef {
Relation::Artist.def()
}
}
impl Related<super::track::Entity> for Entity {
fn to() -> RelationDef {
Relation::Track.def()
}
}
impl ActiveModelBehavior for ActiveModel {}

View file

@ -4,5 +4,7 @@ pub mod prelude;
pub mod album; pub mod album;
pub mod artist; pub mod artist;
pub mod cover_art;
pub mod music_folder; pub mod music_folder;
pub mod track;
pub mod user; pub mod user;

View file

@ -2,5 +2,7 @@
pub use super::album::Entity as Album; pub use super::album::Entity as Album;
pub use super::artist::Entity as Artist; pub use super::artist::Entity as Artist;
pub use super::cover_art::Entity as CoverArt;
pub use super::music_folder::Entity as MusicFolder; pub use super::music_folder::Entity as MusicFolder;
pub use super::track::Entity as Track;
pub use super::user::Entity as User; pub use super::user::Entity as User;

72
entities/src/track.rs Normal file
View file

@ -0,0 +1,72 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.3
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
#[sea_orm(table_name = "track")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i64,
pub title: String,
pub album_id: Option<i64>,
pub artist_id: Option<i64>,
pub is_dir: bool,
pub cover_art_id: Option<i64>,
pub created: DateTimeWithTimeZone,
pub duration: i64,
pub bit_rate: i64,
pub size: i64,
pub suffix: String,
pub content_type: String,
pub is_video: bool,
pub path: String,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(
belongs_to = "super::album::Entity",
from = "Column::AlbumId",
to = "super::album::Column::Id",
on_update = "Cascade",
on_delete = "Cascade"
)]
Album,
#[sea_orm(
belongs_to = "super::artist::Entity",
from = "Column::ArtistId",
to = "super::artist::Column::Id",
on_update = "Cascade",
on_delete = "Cascade"
)]
Artist,
#[sea_orm(
belongs_to = "super::cover_art::Entity",
from = "Column::CoverArtId",
to = "super::cover_art::Column::Id",
on_update = "Cascade",
on_delete = "Cascade"
)]
CoverArt,
}
impl Related<super::album::Entity> for Entity {
fn to() -> RelationDef {
Relation::Album.def()
}
}
impl Related<super::artist::Entity> for Entity {
fn to() -> RelationDef {
Relation::Artist.def()
}
}
impl Related<super::cover_art::Entity> for Entity {
fn to() -> RelationDef {
Relation::CoverArt.def()
}
}
impl ActiveModelBehavior for ActiveModel {}

View file

@ -2,8 +2,10 @@ pub use sea_orm_migration::prelude::*;
mod m20220101_000001_create_user; mod m20220101_000001_create_user;
mod m20231009_181004_create_music_folder; mod m20231009_181004_create_music_folder;
mod m20231009_181104_create_artist; mod m20231009_181104_create_cover_art;
mod m20231009_181204_create_artist;
mod m20231009_181346_create_album; mod m20231009_181346_create_album;
mod m20231009_185712_create_track;
pub struct Migrator; pub struct Migrator;
@ -13,8 +15,10 @@ impl MigratorTrait for Migrator {
vec![ vec![
Box::new(m20220101_000001_create_user::Migration), Box::new(m20220101_000001_create_user::Migration),
Box::new(m20231009_181004_create_music_folder::Migration), Box::new(m20231009_181004_create_music_folder::Migration),
Box::new(m20231009_181104_create_artist::Migration), Box::new(m20231009_181104_create_cover_art::Migration),
Box::new(m20231009_181204_create_artist::Migration),
Box::new(m20231009_181346_create_album::Migration), Box::new(m20231009_181346_create_album::Migration),
Box::new(m20231009_185712_create_track::Migration),
] ]
} }
} }

View file

@ -0,0 +1,40 @@
use sea_orm_migration::prelude::*;
#[derive(DeriveMigrationName)]
pub struct Migration;
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.create_table(
Table::create()
.table(CoverArt::Table)
.if_not_exists()
.col(
ColumnDef::new(CoverArt::Id)
.integer()
.not_null()
.auto_increment()
.primary_key()
.unique_key(),
)
.col(ColumnDef::new(CoverArt::Path).string().not_null())
.to_owned(),
)
.await
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.drop_table(Table::drop().table(CoverArt::Table).to_owned())
.await
}
}
#[derive(DeriveIden)]
pub enum CoverArt {
Table,
Id,
Path,
}

View file

@ -1,5 +1,7 @@
use sea_orm_migration::prelude::*; use sea_orm_migration::prelude::*;
use crate::m20231009_181104_create_cover_art::CoverArt;
#[derive(DeriveMigrationName)] #[derive(DeriveMigrationName)]
pub struct Migration; pub struct Migration;
@ -25,7 +27,7 @@ impl MigrationTrait for Migration {
.not_null() .not_null()
.unique_key(), .unique_key(),
) )
.col(ColumnDef::new(Artist::CoverArt).string().null()) .col(ColumnDef::new(Artist::CoverArtId).big_integer().null())
.col(ColumnDef::new(Artist::ArtistImageUrl).string().null()) .col(ColumnDef::new(Artist::ArtistImageUrl).string().null())
.col( .col(
ColumnDef::new(Artist::AlbumCount) ColumnDef::new(Artist::AlbumCount)
@ -41,7 +43,21 @@ impl MigrationTrait for Migration {
) )
.to_owned(), .to_owned(),
) )
.await .await?;
manager
.create_foreign_key(
ForeignKey::create()
.from_tbl(Artist::Table)
.from_col(Artist::CoverArtId)
.to_tbl(CoverArt::Table)
.to_col(CoverArt::Id)
.on_delete(ForeignKeyAction::SetNull)
.to_owned(),
)
.await?;
Ok(())
} }
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
@ -56,7 +72,7 @@ pub enum Artist {
Table, Table,
Id, Id,
Name, Name,
CoverArt, CoverArtId,
ArtistImageUrl, ArtistImageUrl,
AlbumCount, AlbumCount,
Starred, Starred,

View file

@ -1,7 +1,8 @@
use sea_orm_migration::prelude::*; use sea_orm_migration::prelude::*;
use crate::{ use crate::{
m20231009_181004_create_music_folder::MusicFolder, m20231009_181104_create_artist::Artist, m20231009_181004_create_music_folder::MusicFolder, m20231009_181104_create_cover_art::CoverArt,
m20231009_181204_create_artist::Artist,
}; };
#[derive(DeriveMigrationName)] #[derive(DeriveMigrationName)]
@ -23,9 +24,8 @@ impl MigrationTrait for Migration {
.primary_key(), .primary_key(),
) )
.col(ColumnDef::new(Album::Name).string().not_null()) .col(ColumnDef::new(Album::Name).string().not_null())
.col(ColumnDef::new(Album::Artist).string().null())
.col(ColumnDef::new(Album::ArtistId).big_integer().null()) .col(ColumnDef::new(Album::ArtistId).big_integer().null())
.col(ColumnDef::new(Album::CoverArt).string().null()) .col(ColumnDef::new(Album::CoverArtId).big_integer().null())
.col(ColumnDef::new(Album::SongCount).integer().not_null()) .col(ColumnDef::new(Album::SongCount).integer().not_null())
.col(ColumnDef::new(Album::Duration).big_integer().not_null()) .col(ColumnDef::new(Album::Duration).big_integer().not_null())
.col( .col(
@ -75,6 +75,18 @@ impl MigrationTrait for Migration {
) )
.await?; .await?;
manager
.create_foreign_key(
ForeignKey::create()
.from_tbl(Album::Table)
.from_col(Album::CoverArtId)
.to_tbl(CoverArt::Table)
.to_col(CoverArt::Id)
.on_delete(ForeignKeyAction::SetNull)
.to_owned(),
)
.await?;
Ok(()) Ok(())
} }
@ -90,9 +102,8 @@ pub enum Album {
Table, Table,
Id, Id,
Name, Name,
Artist,
ArtistId, ArtistId,
CoverArt, CoverArtId,
SongCount, SongCount,
Duration, Duration,
PlayCount, PlayCount,

View file

@ -0,0 +1,124 @@
use sea_orm_migration::prelude::*;
use crate::{
m20231009_181104_create_cover_art::CoverArt, m20231009_181204_create_artist::Artist,
m20231009_181346_create_album::Album,
};
#[derive(DeriveMigrationName)]
pub struct Migration;
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.create_table(
Table::create()
.table(Track::Table)
.if_not_exists()
.col(
ColumnDef::new(Track::Id)
.big_integer()
.not_null()
.auto_increment()
.primary_key()
.unique_key(),
)
.col(ColumnDef::new(Track::Title).string().not_null())
.col(ColumnDef::new(Track::AlbumId).big_integer().null())
.col(ColumnDef::new(Track::ArtistId).big_integer().null())
.col(
ColumnDef::new(Track::IsDir)
.boolean()
.not_null()
.default(false),
)
.col(ColumnDef::new(Track::CoverArtId).big_integer().null())
.col(
ColumnDef::new(Track::Created)
.timestamp_with_time_zone()
.not_null(),
)
.col(ColumnDef::new(Track::Duration).big_integer().not_null())
.col(ColumnDef::new(Track::BitRate).big_integer().not_null())
.col(ColumnDef::new(Track::Size).big_integer().not_null())
.col(ColumnDef::new(Track::Suffix).string().not_null())
.col(ColumnDef::new(Track::ContentType).string().not_null())
.col(
ColumnDef::new(Track::IsVideo)
.boolean()
.not_null()
.default(false),
)
.col(ColumnDef::new(Track::Path).string().not_null())
.to_owned(),
)
.await?;
manager
.create_foreign_key(
ForeignKey::create()
.from_tbl(Track::Table)
.from_col(Track::AlbumId)
.to_tbl(Album::Table)
.to_col(Album::Id)
.on_delete(ForeignKeyAction::Cascade)
.on_update(ForeignKeyAction::Cascade)
.to_owned(),
)
.await?;
manager
.create_foreign_key(
ForeignKey::create()
.from_tbl(Track::Table)
.from_col(Track::ArtistId)
.to_tbl(Artist::Table)
.to_col(Artist::Id)
.on_delete(ForeignKeyAction::Cascade)
.on_update(ForeignKeyAction::Cascade)
.to_owned(),
)
.await?;
manager
.create_foreign_key(
ForeignKey::create()
.from_tbl(Track::Table)
.from_col(Track::CoverArtId)
.to_tbl(CoverArt::Table)
.to_col(CoverArt::Id)
.on_delete(ForeignKeyAction::Cascade)
.on_update(ForeignKeyAction::Cascade)
.to_owned(),
)
.await?;
Ok(())
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.drop_table(Table::drop().table(Track::Table).to_owned())
.await
}
}
#[derive(DeriveIden)]
pub enum Track {
Table,
Id,
Title,
AlbumId,
ArtistId,
IsDir,
CoverArtId,
Created,
Duration,
BitRate,
Size,
Suffix,
ContentType,
IsVideo,
Path,
}

View file

@ -3,6 +3,7 @@ name = "rave"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
publish = ["forge-lys-ee"] publish = ["forge-lys-ee"]
default-run = "rave"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

View file

@ -0,0 +1,56 @@
use crate::{
authentication::Authentication,
subsonic::{Error, SubsonicResponse},
utils,
};
use entities::prelude::{Album, Track};
use poem::web::{Data, Query};
use poem_ext::db::DbTxn;
use sea_orm::{EntityTrait, ModelTrait};
use serde::Deserialize;
use tracing::warn;
#[poem::handler]
pub async fn get_album(
Data(txn): Data<&DbTxn>,
auth: Authentication,
Query(params): Query<GetAlbumParams>,
) -> SubsonicResponse {
let txn = txn.clone();
let u = utils::verify_user(txn.clone(), auth).await;
match u {
Ok(_) => {}
Err(e) => return e,
}
let album = Album::find_by_id(params.id).one(&*txn).await;
let Ok(Some(album)) = album else {
match album {
Ok(Some(_)) => unreachable!("Some(album) covered by `let .. else`"),
Ok(None) => return SubsonicResponse::new_error(Error::RequestedDataWasNotFound(None)),
Err(e) => {
warn!("Error getting album: {}", e);
return SubsonicResponse::new_error(Error::Generic(None));
}
}
};
let tracks = album.find_related(Track).all(&*txn).await;
let tracks = match tracks {
Ok(t) => t,
Err(e) => {
warn!("Error getting tracks: {}", e);
return SubsonicResponse::new_error(Error::Generic(None));
}
};
SubsonicResponse::new_album(album, tracks)
}
#[derive(Debug, Clone, Deserialize)]
pub struct GetAlbumParams {
pub id: i32,
}

View file

@ -1,10 +1,17 @@
#![allow(clippy::unused_async)] // todo: remove #![allow(clippy::unused_async)] // todo: remove
use entities::{album, prelude::Album}; use entities::{
use poem::web::{Data, Query}; album, artist,
prelude::{Album, Artist},
};
use poem::{
web::{Data, Query},
Request,
};
use poem_ext::db::DbTxn; use poem_ext::db::DbTxn;
use sea_orm::{ColumnTrait, EntityTrait, QueryFilter, QueryOrder, QuerySelect}; use sea_orm::{ColumnTrait, EntityTrait, QueryFilter, QueryOrder, QuerySelect};
use serde::Deserialize; use serde::Deserialize;
use tracing::debug;
use crate::{ use crate::{
authentication::Authentication, authentication::Authentication,
@ -24,6 +31,7 @@ macro_rules! error_or {
#[poem::handler] #[poem::handler]
pub async fn get_album_list( pub async fn get_album_list(
req: &Request,
Data(txn): Data<&DbTxn>, Data(txn): Data<&DbTxn>,
auth: Authentication, auth: Authentication,
Query(params): Query<GetAlbumListParams>, Query(params): Query<GetAlbumListParams>,
@ -55,7 +63,14 @@ pub async fn get_album_list(
}; };
match album_list { match album_list {
Ok(a) => SubsonicResponse::new_album_list(a), Ok(a) => {
debug!("uri path: {}", req.uri().path());
if req.uri().path().contains("getAlbumList2") {
SubsonicResponse::new_album_list2(a)
} else {
SubsonicResponse::new_album_list(a)
}
}
Err(e) => SubsonicResponse::new_error(e), Err(e) => SubsonicResponse::new_error(e),
} }
} }
@ -141,12 +156,16 @@ async fn get_album_list_alphabetical_by_artist(
params: GetAlbumListParams, params: GetAlbumListParams,
) -> Result<Vec<album::Model>, Error> { ) -> Result<Vec<album::Model>, Error> {
let albums = Album::find() let albums = Album::find()
.order_by_desc(album::Column::Artist) .filter(album::Column::ArtistId.is_not_null())
.find_also_related(Artist)
.order_by_desc(artist::Column::Name)
.limit(params.size) .limit(params.size)
.offset(params.offset) .offset(params.offset)
.all(&*conn) .all(&*conn)
.await; .await;
let albums = albums.map(|c| c.into_iter().map(|c| c.0).collect());
error_or!(albums) error_or!(albums)
} }

View file

@ -2,7 +2,7 @@
use std::fmt::Display; use std::fmt::Display;
use entities::album; use entities::{album, track};
use poem::{http::StatusCode, IntoResponse, Response}; use poem::{http::StatusCode, IntoResponse, Response};
use serde::{ser::SerializeStruct, Serialize}; use serde::{ser::SerializeStruct, Serialize};
use time::OffsetDateTime; use time::OffsetDateTime;
@ -51,7 +51,7 @@ impl SubsonicResponse {
Self::new(SubResponseType::AlbumList2 { albums }) Self::new(SubResponseType::AlbumList2 { albums })
} }
pub fn new_album(album: album::Model, songs: Vec<Child>) -> Self { pub fn new_album(album: album::Model, songs: Vec<track::Model>) -> Self {
Self::new(SubResponseType::Album { album, songs }) Self::new(SubResponseType::Album { album, songs })
} }
@ -98,7 +98,7 @@ pub enum SubResponseType {
#[serde(flatten)] #[serde(flatten)]
album: album::Model, album: album::Model,
#[serde(flatten)] #[serde(flatten)]
songs: Vec<Child>, songs: Vec<track::Model>,
}, },
Empty, Empty,
} }