Compare commits

..

1 Commits

Author SHA1 Message Date
Connor Johnstone
c6452609d6 Formatting 2026-03-18 15:35:58 -04:00
8 changed files with 60 additions and 33 deletions

View File

@@ -33,9 +33,7 @@ impl Database {
.min_connections(1)
.sqlx_logging(false);
let conn = SeaDatabase::connect(opts)
.await
.map_err(DbError::SeaOrm)?;
let conn = SeaDatabase::connect(opts).await.map_err(DbError::SeaOrm)?;
// Enable WAL mode for better concurrent read performance
if database_url.starts_with("sqlite:") && !database_url.contains(":memory:") {

View File

@@ -74,6 +74,7 @@ impl MigrationTrait for Migration {
}
#[derive(DeriveIden)]
#[allow(clippy::enum_variant_names)]
pub(crate) enum Artists {
Table,
Id,

View File

@@ -21,7 +21,12 @@ impl MigrationTrait for Migration {
.auto_increment()
.primary_key(),
)
.col(ColumnDef::new(Tracks::FilePath).text().not_null().unique_key())
.col(
ColumnDef::new(Tracks::FilePath)
.text()
.not_null()
.unique_key(),
)
.col(ColumnDef::new(Tracks::Title).text())
.col(ColumnDef::new(Tracks::Artist).text())
.col(ColumnDef::new(Tracks::Album).text())

View File

@@ -10,15 +10,14 @@ pub async fn upsert(
musicbrainz_id: Option<&str>,
artist_id: Option<i32>,
) -> DbResult<Album> {
if let Some(mbid) = musicbrainz_id {
if let Some(existing) = Albums::find()
if let Some(mbid) = musicbrainz_id
&& let Some(existing) = Albums::find()
.filter(album::Column::MusicbrainzId.eq(mbid))
.one(db)
.await?
{
return Ok(existing);
}
}
if let Some(existing) = find_by_name_and_artist(db, name, album_artist).await? {
return Ok(existing);

View File

@@ -4,17 +4,20 @@ use sea_orm::*;
use crate::entities::artist::{self, ActiveModel, Entity as Artists, Model as Artist};
use crate::error::{DbError, DbResult};
pub async fn upsert(db: &DatabaseConnection, name: &str, musicbrainz_id: Option<&str>) -> DbResult<Artist> {
pub async fn upsert(
db: &DatabaseConnection,
name: &str,
musicbrainz_id: Option<&str>,
) -> DbResult<Artist> {
// Try to find by musicbrainz_id first, then by name
if let Some(mbid) = musicbrainz_id {
if let Some(existing) = Artists::find()
if let Some(mbid) = musicbrainz_id
&& let Some(existing) = Artists::find()
.filter(artist::Column::MusicbrainzId.eq(mbid))
.one(db)
.await?
{
return Ok(existing);
}
}
if let Some(existing) = find_by_name(db, name).await? {
// Update musicbrainz_id if we have one now and didn't before
@@ -80,14 +83,22 @@ pub async fn update(db: &DatabaseConnection, id: i32, model: ActiveModel) -> DbR
Ok(active.update(db).await?)
}
pub async fn update_top_songs(db: &DatabaseConnection, id: i32, top_songs_json: &str) -> DbResult<Artist> {
pub async fn update_top_songs(
db: &DatabaseConnection,
id: i32,
top_songs_json: &str,
) -> DbResult<Artist> {
let existing = get_by_id(db, id).await?;
let mut active: ActiveModel = existing.into();
active.top_songs = Set(top_songs_json.to_string());
Ok(active.update(db).await?)
}
pub async fn update_similar_artists(db: &DatabaseConnection, id: i32, similar_json: &str) -> DbResult<Artist> {
pub async fn update_similar_artists(
db: &DatabaseConnection,
id: i32,
similar_json: &str,
) -> DbResult<Artist> {
let existing = get_by_id(db, id).await?;
let mut active: ActiveModel = existing.into();
active.similar_artists = Set(similar_json.to_string());

View File

@@ -106,7 +106,11 @@ pub async fn get_needing_metadata(db: &DatabaseConnection) -> DbResult<Vec<Track
.await?)
}
pub async fn update_metadata(db: &DatabaseConnection, id: i32, model: ActiveModel) -> DbResult<Track> {
pub async fn update_metadata(
db: &DatabaseConnection,
id: i32,
model: ActiveModel,
) -> DbResult<Track> {
let mut active = model;
active.id = Set(id);
active.updated_at = Set(Utc::now().naive_utc());

View File

@@ -1,6 +1,6 @@
use chrono::Utc;
use sea_orm::*;
use sea_orm::sea_query::Expr;
use sea_orm::*;
use crate::entities::wanted_item::{
self, ActiveModel, Entity as WantedItems, ItemType, Model as WantedItem, WantedStatus,

View File

@@ -5,7 +5,9 @@ use shanty_db::entities::wanted_item::{ItemType, WantedStatus};
use shanty_db::{Database, queries};
async fn test_db() -> Database {
Database::new("sqlite::memory:").await.expect("failed to create test database")
Database::new("sqlite::memory:")
.await
.expect("failed to create test database")
}
#[tokio::test]
@@ -163,7 +165,15 @@ async fn test_wanted_items_lifecycle() {
.unwrap();
// Add wanted item
let item = queries::wanted::add(conn, ItemType::Artist, "Radiohead", None, Some(artist.id), None, None)
let item = queries::wanted::add(
conn,
ItemType::Artist,
"Radiohead",
None,
Some(artist.id),
None,
None,
)
.await
.unwrap();
assert_eq!(item.status, WantedStatus::Wanted);
@@ -219,12 +229,7 @@ async fn test_download_queue_lifecycle() {
assert!(next.is_none());
// Fail it
queries::downloads::update_status(
conn,
item.id,
DownloadStatus::Failed,
Some("network error"),
)
queries::downloads::update_status(conn, item.id, DownloadStatus::Failed, Some("network error"))
.await
.unwrap();
@@ -236,7 +241,9 @@ async fn test_download_queue_lifecycle() {
assert_eq!(failed[0].error_message.as_deref(), Some("network error"));
// Retry
queries::downloads::retry_failed(conn, item.id).await.unwrap();
queries::downloads::retry_failed(conn, item.id)
.await
.unwrap();
let pending = queries::downloads::list(conn, Some(DownloadStatus::Pending))
.await
.unwrap();
@@ -286,7 +293,9 @@ async fn test_search_cache_ttl() {
.await
.unwrap();
let purged = queries::cache::purge_prefix(conn, "artist_totals:").await.unwrap();
let purged = queries::cache::purge_prefix(conn, "artist_totals:")
.await
.unwrap();
assert_eq!(purged, 2);
// other_key should still exist