Compare commits

...

3 Commits

Author SHA1 Message Date
KD dbda9a3897 Get songs endpoint (#162)
* Added test_migrations directory

* Added skeleton endpoint to get songs

* Code formatting

* Added more code

* Added TODO

* Code formatting

* Updated album in album json file

* Changing directory name

* Test refactoring

* Forgot to include this

* Added test for getting songs and added function for test_migrations

* Adding test migrations

* Removing placeholder

* Renamed

* Renamed

* Fixed what caused test failure

* Created migration with cli command

Creating test_migrations/20250725213448_migration_name.sql

* Migration changes

* Removing migration

* More migration changes

* Made endpoint available

* Migration changes

* Got the test to work

* Cleaned up test

* Code formatting

* More cleanup

* Version bump
2025-07-25 18:20:28 -04:00
KD a987e438c4 Added flac files used for testing (#161)
* Added flac files used for testing

* Refactor test

* Added CoverArt file for test files

* Added album json file for testing

* Modifying test json file

* Modifying test file for consistency

* Test refactoring

* Test file changes

* Removing commented code

* Removing files used for testing

* Removing placeholder

* Code formatting
2025-07-25 16:09:01 -04:00
KD a150d34c6c Added release workflow (#160)
* Added release workflow

* Temp change

* File extension change

* Syntax change

* Removing that temp change
2025-07-22 21:59:20 -04:00
22 changed files with 354 additions and 28 deletions
+50
View File
@@ -0,0 +1,50 @@
name: Release Tagging
on:
push:
branches: [ "v0.2" ]
jobs:
release:
runs-on: ubuntu-24.04
steps:
- name: Checkout code
uses: actions/checkout@v3
with:
fetch-depth: 0 # Important for git describe --tags
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: 1.88.0
components: cargo
- name: Extract Version from Cargo.toml
id: version
run: |
VERSION=$(grep '^version = "' Cargo.toml | sed -E 's/version = "([^"]+)"/\1/')
PROJECT_COMMIT_HASH=$(git rev-parse HEAD | cut -c 1-10)
BRANCH_REF="${GITHUB_REF}"
BRANCH_NAME=$(echo "$BRANCH_REF" | cut -d '/' -f 3)
PROJECT_TAG_RELEASE="v$VERSION-$BRANCH_NAME-$PROJECT_COMMIT_HASH"
echo "::set-output name=project_tag_release::$PROJECT_TAG_RELEASE-961"
echo "Version: $VERSION"
echo "Hash: $PROJECT_COMMIT_HASH"
echo "Branch: $BRANCH_NAME"
echo "Tag Release: $PROJECT_TAG_RELEASE"
- name: Print version
id: print_version
run: |
echo "Printing version"
echo "Version: ${{ steps.version.outputs.project_tag_release }}"
- name: Create GitHub Release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ steps.version.outputs.project_tag_release }}
release_name: Release ${{ steps.version.outputs.project_tag_release }}
body: |
Release of version ${{ steps.version.outputs.project_tag_release }}
+8
View File
@@ -0,0 +1,8 @@
{
"dev": {
"migrations": "./migrations"
},
"test": {
"migrations": "./test_migrations"
}
}
Generated
+1 -1
View File
@@ -752,7 +752,7 @@ dependencies = [
[[package]]
name = "icarus"
version = "0.1.93"
version = "0.1.94"
dependencies = [
"axum",
"common-multipart-rfc7578",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "icarus"
version = "0.1.93"
version = "0.1.94"
edition = "2024"
rust-version = "1.88"
+1
View File
@@ -16,6 +16,7 @@ pub mod endpoints {
pub const QUEUECOVERARTDATAWIPE: &str = "/api/v2/coverart/queue/data/wipe";
pub const CREATESONG: &str = "/api/v2/song";
pub const GETSONGS: &str = "/api/v2/song";
pub const CREATECOVERART: &str = "/api/v2/coverart";
}
+43
View File
@@ -92,6 +92,13 @@ pub mod request {
pub user_id: uuid::Uuid,
}
}
pub mod get_songs {
#[derive(Debug, Default, serde::Deserialize, serde::Serialize)]
pub struct Params {
pub id: Option<uuid::Uuid>,
}
}
}
pub mod response {
@@ -158,6 +165,14 @@ pub mod response {
pub data: Vec<uuid::Uuid>,
}
}
pub mod get_songs {
#[derive(Debug, Default, serde::Deserialize, serde::Serialize)]
pub struct Response {
pub message: String,
pub data: Vec<icarus_models::song::Song>,
}
}
}
// TODO: Might make a distinction between year and date in a song's tag at some point
@@ -968,4 +983,32 @@ pub mod endpoint {
}
}
}
pub async fn get_songs(
axum::Extension(pool): axum::Extension<sqlx::PgPool>,
axum::extract::Query(params): axum::extract::Query<super::request::get_songs::Params>,
) -> (
axum::http::StatusCode,
Json<super::response::get_songs::Response>,
) {
let mut response = super::response::get_songs::Response::default();
match params.id {
Some(id) => match super::song_db::get_song(&pool, &id).await {
Ok(song) => {
response.message = String::from(super::super::response::SUCCESSFUL);
response.data.push(song);
(axum::http::StatusCode::OK, axum::Json(response))
}
Err(err) => {
response.message = err.to_string();
(axum::http::StatusCode::BAD_REQUEST, axum::Json(response))
}
},
None => {
response.message = String::from("Invalid parameters");
(axum::http::StatusCode::BAD_REQUEST, axum::Json(response))
}
}
}
}
+112 -26
View File
@@ -115,12 +115,17 @@ pub mod init {
crate::callers::endpoints::CREATECOVERART,
post(crate::callers::coverart::endpoint::create_coverart),
)
.route(
crate::callers::endpoints::GETSONGS,
get(crate::callers::song::endpoint::get_songs),
)
}
pub async fn app() -> axum::Router {
let pool = crate::db::create_pool()
.await
.expect("Failed to create pool");
// TODO: Look into handling this. Seems redundant to run migrations multiple times
crate::db::migrations(&pool).await;
routes()
@@ -227,6 +232,15 @@ mod tests {
Err("Error parsing".into())
}
}
pub async fn migrations(pool: &sqlx::PgPool) {
// Run migrations using the sqlx::migrate! macro
// Assumes your test migrations are in a ./test_migrations folder relative to Cargo.toml
sqlx::migrate!("./test_migrations")
.run(pool)
.await
.expect("Failed to run migrations");
}
}
mod init {
@@ -247,7 +261,7 @@ mod tests {
) -> Result<axum::response::Response, std::convert::Infallible> {
// Create multipart form
let mut form = MultipartForm::default();
let _ = form.add_file("flac", "tests/Machine_gun/track01.flac");
let _ = form.add_file("flac", "tests/I/track01.flac");
// Create request
let content_type = form.content_type();
@@ -331,7 +345,7 @@ mod tests {
app: &axum::Router,
) -> Result<axum::response::Response, std::convert::Infallible> {
let mut form = MultipartForm::default();
let _ = form.add_file("jpg", "tests/Machine_gun/160809_machinegun.jpg");
let _ = form.add_file("jpg", "tests/I/Coverart.jpg");
// Create request
let content_type = form.content_type();
@@ -407,22 +421,7 @@ mod tests {
song_queue_id: &uuid::Uuid,
user_id: &uuid::Uuid,
) -> Result<axum::response::Response, std::convert::Infallible> {
let payload = serde_json::json!({
"title": "Power of Soul",
"artist": "Jimmi Hendrix",
"album_artist": "Jimmi Hendrix",
"album": "Machine Gun",
"genre": "Psychadelic Rock",
"date": "2016-01-01",
"track": 1,
"disc": 1,
"track_count": 11,
"disc_count": 1,
"duration": 330,
"audio_type": "flac",
"user_id": user_id,
"song_queue_id": song_queue_id
});
let payload = payload_data::create_song(song_queue_id, user_id).await;
let req = axum::http::Request::builder()
.method(axum::http::Method::POST)
@@ -653,17 +652,39 @@ mod tests {
serde_json::json!(
{
"song_queue_id": song_queue_id,
"album" : "Machine Gun: The FillMore East First Show",
"album_artist" : "Jimi Hendrix",
"artist" : "Jimi Hendrix",
"album" : "I",
"album_artist" : "Kuoth",
"artist" : "Kuoth",
"disc" : 1,
"disc_count" : 1,
"duration" : 330,
"genre" : "Psychadelic Rock",
"title" : "Power of Soul",
"duration" : 139,
"genre" : "Alternative Hip-Hop",
"title" : "Hypocrite Like The Rest",
"track" : 1,
"track_count" : 11,
"year" : 2016
"track_count" : 9,
"year" : 2020
})
}
pub async fn create_song(
song_queue_id: &uuid::Uuid,
user_id: &uuid::Uuid,
) -> serde_json::Value {
serde_json::json!({
"title" : "Hypocrite Like The Rest",
"artist" : "Kuoth",
"album_artist": "Kuoth",
"album": "I",
"genre" : "Alternative Hip-Hop",
"date": "2020-01-01",
"track": 1,
"disc": 1,
"track_count": 9,
"disc_count": 1,
"duration": 139,
"audio_type": "flac",
"user_id": user_id,
"song_queue_id": song_queue_id
})
}
}
@@ -1792,4 +1813,69 @@ mod tests {
let _ = db_mgr::drop_database(&tm_pool, &db_name).await;
}
pub mod after_song_queue {
use tower::ServiceExt;
#[tokio::test]
async fn test_get_songs() {
let tm_pool = super::db_mgr::get_pool().await.unwrap();
let db_name = super::db_mgr::generate_db_name().await;
match super::db_mgr::create_database(&tm_pool, &db_name).await {
Ok(_) => {
println!("Success");
}
Err(err) => {
assert!(false, "Error: {:?}", err);
}
}
let pool = super::db_mgr::connect_to_db(&db_name).await.unwrap();
super::db_mgr::migrations(&pool).await;
let app = super::init::app(pool).await;
let mut id = uuid::Uuid::nil();
match uuid::Uuid::parse_str("44cf7940-34ff-489f-9124-d0ec90a55af9") {
Ok(val) => {
id = val;
}
Err(err) => {
assert!(false, "Error: {err:?}");
}
};
let uri = format!("{}?id={id}", crate::callers::endpoints::GETSONGS);
match app
.clone()
.oneshot(
axum::http::Request::builder()
.method(axum::http::Method::GET)
.uri(uri)
.header(axum::http::header::CONTENT_TYPE, "application/json")
.body(axum::body::Body::empty())
.unwrap(),
)
.await
{
Ok(response) => {
let resp = super::get_resp_data::<
crate::callers::song::response::get_songs::Response,
>(response)
.await;
assert_eq!(false, resp.data.is_empty(), "Should not be empty");
let song = resp.data[0].clone();
assert_eq!(id, song.id, "Id does not match {song:?}");
}
Err(err) => {
assert!(false, "Error: {err:?}");
}
}
let _ = super::db_mgr::drop_database(&tm_pool, &db_name).await;
}
}
}
+62
View File
@@ -0,0 +1,62 @@
-- Add migration script here
CREATE EXTENSION IF NOT EXISTS pgcrypto;
-- Table to store queued songs to process
CREATE TABLE IF NOT EXISTS "songQueue" (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
filename TEXT NOT NULL,
status TEXT CHECK (status IN ('pending', 'ready', 'processing', 'done')),
data BYTEA NULL,
user_id UUID NULL
);
-- Table to store queued metadata
CREATE TABLE IF NOT EXISTS "metadataQueue" (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
metadata jsonb NOT NULL,
created_at timestamptz DEFAULT now(),
song_queue_id UUID NOT NULL
);
-- Table to store queued coverart
CREATE TABLE IF NOT EXISTS "coverartQueue" (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
data BYTEA NULL,
song_queue_id UUID NULL
);
-- Create an index for better query performance
CREATE INDEX metadata_queue_data_metadata ON "metadataQueue" USING gin (metadata);
-- Table to store a song's info
CREATE TABLE IF NOT EXISTS "song" (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
title TEXT NOT NULL,
artist TEXT NOT NULL,
album_artist TEXT NOT NULL,
album TEXT NOT NULL,
genre TEXT NOT NULL,
year INT NOT NULL,
track INT NOT NULL,
disc INT NOT NULL,
track_count INT NOT NULL,
disc_count INT NOT NULL,
duration INT NOT NULL,
audio_type TEXT NOT NULL,
date_created timestamptz DEFAULT now(),
filename TEXT NOT NULL,
directory TEXT NOT NULL,
user_id UUID NULL
);
CREATE TABLE IF NOT EXISTS "coverart" (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
title TEXT NOT NULL,
path TEXT NOT NULL,
song_id UUID NOT NULL
);
-- Might not to disable constraints on fields
-- INSERT INTO "song" (id, title, artist, album_artist, album, genre, year, track, disc, track_count, disc_count, duration, audio_type, date_created, filename, directory, user_id) VALUES('44cf7940-34ff-489f-9124-d0ec90a55af9', 'Hypocrite Like The Rest', 'Kuoth', 'Kuoth', 'I', 'Alternative Hip-Hop', 2020, 1, 1, 9, 1, 139, 'flac', '2020-01-01 13:00:00-05', 'track01.flac', 'tests/I', '47491f9b-725a-4ba4-b9a5-711e1be46670');
-- Re-enable the constraints on the fields
@@ -0,0 +1,3 @@
-- Add migration script here
INSERT INTO "song" (id, title, artist, album_artist, album, genre, year, track, disc, track_count, disc_count, duration, audio_type, date_created, filename, directory, user_id) VALUES('44cf7940-34ff-489f-9124-d0ec90a55af9', 'Hypocrite Like The Rest', 'Kuoth', 'Kuoth', 'I', 'Alternative Hip-Hop', 2020, 1, 1, 9, 1, 139, 'flac', '2020-01-01 13:00:00-05', 'track01.flac', 'tests/I', '47491f9b-725a-4ba4-b9a5-711e1be46670');
INSERT INTO "coverart" VALUES('996122cd-5ae9-4013-9934-60768d3006ed', 'I', 'tests/I/Coverart.jpg', '44cf7940-34ff-489f-9124-d0ec90a55af9');
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 MiB

+73
View File
@@ -0,0 +1,73 @@
{
"album": "I",
"album_artist": "Kuoth",
"disc_count": 1,
"genre": "Alternative Hip-Hop",
"year": 2020,
"track_count": 9,
"tracks": [
{
"artist": "Kuoth",
"disc": 1,
"title": "Hypocrite Like the Rest",
"duration": 139,
"track": 1
},
{
"artist": "Kuoth",
"disc": 1,
"title": "It's Too Late",
"duration": 116,
"track": 2
},
{
"artist": "Kuoth",
"disc": 1,
"title": "Hell Might be Nice",
"duration": 229,
"track": 3
},
{
"artist": "Kuoth",
"disc": 1,
"title": "Dark Side of Progress",
"duration": 187,
"track": 4
},
{
"artist": "Kuoth",
"disc": 1,
"title": "Distractions",
"duration": 141,
"track": 5
},
{
"artist": "Kuoth",
"disc": 1,
"title": "But You Won't Listen",
"duration": 184,
"track": 6
},
{
"artist": "Kuoth",
"disc": 1,
"title": "Guidance",
"duration": 121,
"track": 7
},
{
"artist": "Kuoth",
"disc": 1,
"title": "M",
"duration": 98,
"track": 8
},
{
"artist": "Kuoth",
"disc": 1,
"title": "Social Acceptance",
"duration": 169,
"track": 9
}
]
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 271 KiB