Compare commits

...
10 Commits
Author SHA1 Message Date
phoenix ff3567f8ef S3 song download (#260)
soaricarus_api CI / Rustfmt (push) Successful in 3m46s
soaricarus_api CI / Check (push) Successful in 5m3s
soaricarus_api CI / Test Suite (push) Successful in 6m21s
soaricarus_api CI / Clippy (push) Successful in 6m46s
soaricarus_api CI / build (push) Successful in 8m45s
pr CI / Test Suite (pull_request) Successful in 5m9s
Reviewed-on: #260
2026-08-14 16:41:56 -04:00
phoenix 695faf7502 Coverart queue (#259)
pr CI / Test Suite (pull_request) Failing after 2m42s
soaricarus_api CI / Check (push) Successful in 7m15s
soaricarus_api CI / Rustfmt (push) Successful in 5m25s
soaricarus_api CI / Clippy (push) Failing after 7m1s
soaricarus_api CI / build (push) Successful in 7m47s
soaricarus_api CI / Test Suite (push) Failing after 2m24s
Reviewed-on: #259
2026-08-14 08:42:38 -04:00
phoenix bf88f249ea Song queue (#258)
soaricarus_api CI / Check (push) Successful in 3m59s
soaricarus_api CI / build (push) Successful in 5m6s
soaricarus_api CI / Rustfmt (push) Successful in 5m27s
soaricarus_api CI / Test Suite (push) Successful in 5m50s
soaricarus_api CI / Clippy (push) Successful in 6m26s
Reviewed-on: #258
2026-08-13 15:27:31 -04:00
phoenix 7188138018 Refactoring tests (#257)
soaricarus_api CI / Rustfmt (push) Successful in 4m43s
soaricarus_api CI / Check (push) Successful in 5m26s
soaricarus_api CI / Test Suite (push) Successful in 6m4s
soaricarus_api CI / Clippy (push) Successful in 6m54s
soaricarus_api CI / build (push) Successful in 8m54s
Reviewed-on: #257
2026-08-12 16:53:24 -04:00
phoenix 20bb7f4dad ci: Fix workflow (#256)
soaricarus_api CI / Check (push) Successful in 3m48s
soaricarus_api CI / Test Suite (push) Successful in 4m47s
soaricarus_api CI / Rustfmt (push) Successful in 3m51s
soaricarus_api CI / Clippy (push) Successful in 3m30s
soaricarus_api CI / build (push) Successful in 1m22s
Reviewed-on: #256
2026-08-08 14:50:04 -04:00
phoenix 19d6ec2190 Obj str do it (#255)
soaricarus_api CI / Clippy (push) Successful in 3m59s
soaricarus_api CI / Rustfmt (push) Successful in 4m32s
soaricarus_api CI / Check (push) Successful in 4m44s
soaricarus_api CI / Test Suite (push) Failing after 5m24s
soaricarus_api CI / build (push) Successful in 6m4s
Reviewed-on: #255
2026-08-08 14:29:14 -04:00
phoenix 59a7e31641 Updating name (#253)
soaricarus_api CI / Check (push) Successful in 3m21s
soaricarus_api CI / Test Suite (push) Successful in 4m25s
soaricarus_api CI / Rustfmt (push) Successful in 3m8s
soaricarus_api CI / Clippy (push) Successful in 3m56s
soaricarus_api CI / build (push) Successful in 3m3s
Reviewed-on: #253
2026-07-21 15:28:48 -04:00
phoenix 8a64a71339 Update postgres (#252)
Rust CI / Test Suite (push) Successful in 3m43s
Rust CI / Rustfmt (push) Successful in 2m2s
Rust CI / Check (push) Successful in 3m9s
Rust CI / build (push) Successful in 2m58s
Rust CI / Clippy (push) Successful in 4m28s
Reviewed-on: phoenix/icarus#252
2026-07-09 11:05:12 -04:00
phoenix 7ca4c655a4 Update (#251)
Rust CI / build (push) Failing after 2m53s
Reviewed-on: phoenix/icarus#251
2026-07-07 23:25:45 -04:00
phoenix 50d0ea630f Update rust (#250)
Rust CI / build (pull_request) Failing after 2m21s
* bump: rust Cargo.toml

* bump: rust docker

* bump: rust ci

* bump: axum

* bump: axum-extra

* bump: tokio

* bump: tower-http

* bump: uuid

* bump: utoipa

* bump: icarus_meta

* bump: icarus_models

* bump: icarus_envy

* bump: icarus

* Updated base image in dockerfile

* Adding sample local env file

* Fixing postgresql volume issue in docker
2026-05-22 11:29:38 -04:00
32 changed files with 3383 additions and 1642 deletions
+26 -4
View File
@@ -4,9 +4,31 @@ FRONTEND_URL=http://localhost:4200
RUST_LOG=debug RUST_LOG=debug
ALLOWED_ORIGINS=https://soaricarus.com,https://www.soaricarus.com ALLOWED_ORIGINS=https://soaricarus.com,https://www.soaricarus.com
SECRET_MAIN_KEY=refero34o8rfhfjn983thf39fhc943rf923n3h SECRET_MAIN_KEY=refero34o8rfhfjn983thf39fhc943rf923n3h
ROOT_DIRECTORY=/home/icarus/mydata ROOT_DIRECTORY=/home/soaricarus/mydata
POSTGRES_MAIN_USER=icarus POSTGRES_MAIN_USER=soaricarus
POSTGRES_MAIN_PASSWORD=password POSTGRES_MAIN_PASSWORD=password
POSTGRES_MAIN_DB=icarus_db POSTGRES_MAIN_DB=soaricarus_db
POSTGRES_MAIN_HOST=main_db POSTGRES_MAIN_HOST=main_db
DATABASE_URL=postgres://${POSTGRES_MAIN_USER}:${POSTGRES_MAIN_PASSWORD}@${POSTGRES_MAIN_HOST}:5432/${POSTGRES_MAIN_DB} POSTGRES_MAIN_PORT=5432
DATABASE_URL=postgres://${POSTGRES_MAIN_USER}:${POSTGRES_MAIN_PASSWORD}@${POSTGRES_MAIN_HOST}:${POSTGRES_MAIN_PORT}/${POSTGRES_MAIN_DB}
S3_BUCKET_NAME=soaricarus-bucket
S3_ENDPOINT_URL=http://maze:3900
GARAGE_S3_REGION=maze
GARAGE_RPC_SECRET=c2bde5c3f9c04116a7563e25ea5ba6fab25319daa2fe8a952ad57d0b8159b590
GARAGE_METRICS_TOKEN=sD2g4jiYV66KdZWtIuj2amezuryzv81mZNktpMNcmU4=
GARAGE_DEFAULT_ACCESS_KEY=GK8eb91557b9a18dcbdc834c16a25ac6fc
GARAGE_DEFAULT_SECRET_KEY=WhFAtnAtWgYaa0pMfl9jwygalZSwYaVliY3Ndm7nYHo=
GARAGE_DEFAULT_BUCKET=${S3_BUCKET_NAME}
GARAGE_ADMIN_TOKEN=5yMAjSINa6U04wr4pDbcTYfsXKl3S9ZjKdiPXWYUbNw=
GARAGE_METADATA_DIR=/var/lib/garage/meta
GARAGE_DATA_DIR=/var/lib/garage/data
GARAGE_DB_ENGINE=sqlite
GARAGE_REPLICATION_FACTOR=1
GARAGE_RPC_BIND_ADDR=[::]:3901
GARAGE_S3_API_BIND_ADDR=[::]:3900
GARAGE_S3_WEB_BIND_ADDR=[::]:3902
GARAGE_ROOT_DOMAIN=.localhost
AWS_DEFAULT_REGION=${GARAGE_S3_REGION}
AWS_SECRET_ACCESS_KEY=${GARAGE_DEFAULT_ACCESS_KEY}
AWS_ACCESS_KEY_ID=${GARAGE_DEFAULT_SECRET_KEY}
+34
View File
@@ -0,0 +1,34 @@
APP_ENV=development
BACKEND_PORT=8000
FRONTEND_URL=http://localhost:4200
RUST_LOG=debug
ALLOWED_ORIGINS=https://soaricarus.com,https://www.soaricarus.com
SECRET_MAIN_KEY=refero34o8rfhfjn983thf39fhc943rf923n3h
ROOT_DIRECTORY=/home/soaricarus/mydata
POSTGRES_MAIN_USER=soaricarus
POSTGRES_MAIN_PASSWORD=password
POSTGRES_MAIN_DB=soaricarus_db
POSTGRES_MAIN_HOST=main_db
POSTGRES_MAIN_PORT=5432
DATABASE_URL=postgres://${POSTGRES_MAIN_USER}:${POSTGRES_MAIN_PASSWORD}@${POSTGRES_MAIN_HOST}:${POSTGRES_MAIN_PORT}/${POSTGRES_MAIN_DB}
S3_BUCKET_NAME=soaricarus-bucket
S3_ENDPOINT_URL=http://maze:3900
GARAGE_S3_REGION=maze
GARAGE_RPC_SECRET=c2bde5c3f9c04116a7563e25ea5ba6fab25319daa2fe8a952ad57d0b8159b590
GARAGE_METRICS_TOKEN=sD2g4jiYV66KdZWtIuj2amezuryzv81mZNktpMNcmU4=
GARAGE_DEFAULT_ACCESS_KEY=GK8eb91557b9a18dcbdc834c16a25ac6fc
GARAGE_DEFAULT_SECRET_KEY=WhFAtnAtWgYaa0pMfl9jwygalZSwYaVliY3Ndm7nYHo=
GARAGE_DEFAULT_BUCKET=${S3_BUCKET_NAME}
GARAGE_ADMIN_TOKEN=5yMAjSINa6U04wr4pDbcTYfsXKl3S9ZjKdiPXWYUbNw=
GARAGE_METADATA_DIR=/var/lib/garage/meta
GARAGE_DATA_DIR=/var/lib/garage/data
GARAGE_DB_ENGINE=sqlite
GARAGE_REPLICATION_FACTOR=1
GARAGE_RPC_BIND_ADDR=[::]:3901
GARAGE_S3_API_BIND_ADDR=[::]:3900
GARAGE_S3_WEB_BIND_ADDR=[::]:3902
GARAGE_ROOT_DOMAIN=.localhost
AWS_DEFAULT_REGION=${GARAGE_S3_REGION}
AWS_SECRET_ACCESS_KEY=${GARAGE_DEFAULT_ACCESS_KEY}
AWS_ACCESS_KEY_ID=${GARAGE_DEFAULT_SECRET_KEY}
-12
View File
@@ -1,12 +0,0 @@
APP_ENV=production
BACKEND_PORT=8000
FRONTEND_URL=http://localhost:4200
RUST_LOG=debug
ALLOWED_ORIGINS=https://soaricarus.com,https://www.soaricarus.com,http://localhost:8000,http://127.0.0.1:8000
SECRET_MAIN_KEY=refero34o8rfhfjn983thf39fhc943rf923n3h
ROOT_DIRECTORY=/home/icarus/mydata
POSTGRES_MAIN_USER=icarus
POSTGRES_MAIN_PASSWORD=password
POSTGRES_MAIN_DB=icarus_db
POSTGRES_MAIN_HOST=localhost
DATABASE_URL=postgres://${POSTGRES_MAIN_USER}:${POSTGRES_MAIN_PASSWORD}@${POSTGRES_MAIN_HOST}:5432/${POSTGRES_MAIN_DB}
+116
View File
@@ -0,0 +1,116 @@
name: pr CI
on:
pull_request:
branches: [ "main" ]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
test:
name: Test Suite
runs-on: ubuntu-24.04
timeout-minutes: 20
services:
postgres:
image: postgres:18.4-alpine
env:
POSTGRES_USER: ${{ secrets.DB_TEST_USER || 'testuser' }}
POSTGRES_PASSWORD: ${{ secrets.DB_TEST_PASSWORD || 'testpassword' }}
POSTGRES_DB: ${{ secrets.DB_TEST_NAME || 'testdb' }}
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v6
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: 1.97
- uses: Swatinem/rust-cache@v2
- name: Verify Docker Environment
run: |
echo "Runner User Info:"
id
echo "Checking Docker Version:"
docker --version
echo "Checking Docker Daemon Status (info):"
docker info
echo "Checking Docker Daemon Status (ps):"
docker ps -a
echo "Docker environment check complete."
- name: Setup S3
env:
AWS_ACCESS_KEY_ID: "${{ secrets.GARAGE_KEY_ID }}"
AWS_SECRET_ACCESS_KEY: "${{ secrets.GARAGE_SECRET }}"
AWS_DEFAULT_REGION: us-east-1
ENDPOINT_URL: "${{ secrets.S3_CI_ENDPOINT_URL}}"
run: |
sudo apt update
echo "Installing curl and unzip"
sudo apt install curl unzip -y
echo "Installing awscli"
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install
aws --version
BUCKET="soaricarus-ci-bucket-$(echo $RANDOM | md5sum | head -c 8)"
echo "S3_BUCKET=$BUCKET" >> $GITHUB_ENV
echo "AWS_DEFAULT_REGION=$AWS_DEFAULT_REGION" >> $GITHUB_ENV
echo "AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID" >> $GITHUB_ENV
echo "AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY" >> $GITHUB_ENV
echo "Creating bucket: $BUCKET"
aws --endpoint-url=$ENDPOINT_URL s3 mb s3://$BUCKET --region $AWS_DEFAULT_REGION
- name: Run tests
env:
DATABASE_URL: postgresql://${{ secrets.DB_TEST_USER || 'testuser' }}:${{ secrets.DB_TEST_PASSWORD || 'testpassword' }}@postgres:5432/${{ secrets.DB_TEST_NAME || 'testdb' }}
RUST_LOG: info # Optional: configure test log level
SECRET_MAIN_KEY: ${{ secrets.TOKEN_SECRET_KEY }}
SSH_AUTH_SOCK: ${{ env.SSH_AUTH_SOCK }}
ROOT_DIRECTORY: "/tmp"
AWS_ACCESS_KEY_ID: "${{ secrets.GARAGE_KEY_ID }}"
AWS_SECRET_ACCESS_KEY: "${{ secrets.GARAGE_SECRET }}"
AWS_DEFAULT_REGION: "${{ env.AWS_DEFAULT_REGION }}"
GARAGE_DEFAULT_ACCESS_KEY: "${{ secrets.GARAGE_KEY_ID }}"
GARAGE_DEFAULT_SECRET_KEY: "${{ secrets.GARAGE_SECRET }}"
GARAGE_S3_REGION: "${{ env.AWS_DEFAULT_REGION }}"
S3_BUCKET_NAME: ${{ env.S3_BUCKET }}
S3_ENDPOINT_URL: "${{ secrets.S3_CI_ENDPOINT_URL }}"
AWS_S3_FORCE_PATH_STYLE: "true"
run: |
mkdir -p ~/.ssh
echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/soaricarus_models_deploy_key
chmod 600 ~/.ssh/soaricarus_models_deploy_key
ssh-keyscan ${{ secrets.MYHOST }} >> ~/.ssh/known_hosts
eval $(ssh-agent -s)
ssh-add -v ~/.ssh/soaricarus_models_deploy_key
cargo test
- name: Cleanup
if: always()
run: |
echo "Cleaning up bucket $BUCK"
echo "Region: $AWS_DEFAULT_REGION"
aws --endpoint-url=$ENDPOINT_URL \
s3 rb s3://$BUCK --force \
--region $AWS_DEFAULT_REGION
env:
ENDPOINT_URL: ${{ secrets.S3_CI_ENDPOINT_URL }}
AWS_ACCESS_KEY_ID: "${{ env.AWS_ACCESS_KEY_ID }}"
AWS_SECRET_ACCESS_KEY: "${{ env.AWS_SECRET_ACCESS_KEY }}"
AWS_DEFAULT_REGION: "${{ env.AWS_DEFAULT_REGION }}"
BUCK: ${{ env.S3_BUCKET }}
continue-on-error: true
+2 -2
View File
@@ -9,14 +9,14 @@ jobs:
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v5 uses: actions/checkout@v6
with: with:
fetch-depth: 0 # Important for git describe --tags fetch-depth: 0 # Important for git describe --tags
- name: Install Rust - name: Install Rust
uses: actions-rs/toolchain@v1 uses: actions-rs/toolchain@v1
with: with:
toolchain: 1.94 toolchain: 1.97
components: cargo components: cargo
- name: Extract Version from Cargo.toml - name: Extract Version from Cargo.toml
+165 -49
View File
@@ -1,23 +1,46 @@
name: Rust CI name: soaricarus_api CI
on: on:
push: push:
branches: [ "main" ] branches: [ "main" ]
pull_request:
branches: [ "main" ] concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs: jobs:
build: check:
name: Check
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v6
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: 1.97
- uses: Swatinem/rust-cache@v2
- run: |
mkdir -p ~/.ssh
echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/soaricarus_models_deploy_key
chmod 600 ~/.ssh/soaricarus_models_deploy_key
ssh-keyscan ${{ secrets.MYHOST }} >> ~/.ssh/known_hosts
eval $(ssh-agent -s)
ssh-add -v ~/.ssh/soaricarus_models_deploy_key
cargo check
test:
name: Test Suite
runs-on: ubuntu-24.04
timeout-minutes: 20
services: services:
postgres: postgres:
image: postgres:18.3-alpine image: postgres:18.4-alpine
env: env:
POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }} POSTGRES_USER: ${{ secrets.DB_TEST_USER || 'testuser' }}
POSTGRES_USER: ${{ secrets.POSTGRES_USER }} POSTGRES_PASSWORD: ${{ secrets.DB_TEST_PASSWORD || 'testpassword' }}
POSTGRES_DB: ${{ secrets.POSTGRES_DB }} POSTGRES_DB: ${{ secrets.DB_TEST_NAME || 'testdb' }}
ports:
- 5432:5432
options: >- options: >-
--health-cmd pg_isready --health-cmd pg_isready
--health-interval 10s --health-interval 10s
@@ -25,55 +48,148 @@ jobs:
--health-retries 5 --health-retries 5
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v6
- uses: actions-rust-lang/setup-rust-toolchain@v1
- name: Install Rust 1.94
uses: actions-rs/toolchain@v1
with: with:
toolchain: 1.94 toolchain: 1.97
components: clippy, rustfmt - uses: Swatinem/rust-cache@v2
override: true - name: Verify Docker Environment
run: |
echo "Runner User Info:"
id
echo "Checking Docker Version:"
docker --version
echo "Checking Docker Daemon Status (info):"
docker info
echo "Checking Docker Daemon Status (ps):"
docker ps -a
echo "Docker environment check complete."
- name: Cache dependencies - name: Setup S3
uses: Swatinem/rust-cache@v2
- name: Install libpq
run: sudo apt-get install -y libpq-dev
- name: Setup test database
env: env:
DATABASE_URL: "postgres://${{ secrets.POSTGRES_USER }}:${{ secrets.POSTGRES_PASSWORD }}@localhost:5432/${{ secrets.POSTGRES_DB }}" AWS_ACCESS_KEY_ID: "${{ secrets.GARAGE_KEY_ID }}"
AWS_SECRET_ACCESS_KEY: "${{ secrets.GARAGE_SECRET }}"
AWS_DEFAULT_REGION: us-east-1
ENDPOINT_URL: "${{ secrets.S3_CI_ENDPOINT_URL}}"
run: | run: |
# Wait for PostgreSQL to be ready sudo apt update
for i in {1..10}; do echo "Installing curl and unzip"
pg_isready -U ${{ secrets.POSTGRES_USER }} -d ${{ secrets.POSTGRES_DB }} && break sudo apt install curl unzip -y
sleep 2
done
# Run database migrations (if you use sqlx migrations) echo "Installing awscli"
cargo install sqlx-cli --no-default-features --features native-tls,postgres curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
sqlx migrate run --database-url $DATABASE_URL unzip awscliv2.zip
sudo ./aws/install
aws --version
- name: Build BUCKET="soaricarus-ci-bucket-$(echo $RANDOM | md5sum | head -c 8)"
run: | echo "S3_BUCKET=$BUCKET" >> $GITHUB_ENV
mkdir -p ~/.ssh echo "AWS_DEFAULT_REGION=$AWS_DEFAULT_REGION" >> $GITHUB_ENV
echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/gitea_deploy_key echo "AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID" >> $GITHUB_ENV
chmod 600 ~/.ssh/gitea_deploy_key echo "AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY" >> $GITHUB_ENV
ssh-keyscan ${{ vars.MYHOST }} >> ~/.ssh/known_hosts
eval $(ssh-agent -s) echo "Creating bucket: $BUCKET"
ssh-add -v ~/.ssh/gitea_deploy_key aws --endpoint-url=$ENDPOINT_URL s3 mb s3://$BUCKET --region $AWS_DEFAULT_REGION
cargo build --verbose --release
- name: Run tests - name: Run tests
env: env:
DATABASE_URL: postgresql://${{ secrets.DB_TEST_USER || 'testuser' }}:${{ secrets.DB_TEST_PASSWORD || 'testpassword' }}@postgres:5432/${{ secrets.DB_TEST_NAME || 'testdb' }}
RUST_LOG: info # Optional: configure test log level
SECRET_MAIN_KEY: ${{ secrets.TOKEN_SECRET_KEY }}
SSH_AUTH_SOCK: ${{ env.SSH_AUTH_SOCK }}
ROOT_DIRECTORY: "/tmp" ROOT_DIRECTORY: "/tmp"
DATABASE_URL: "postgres://${{ secrets.POSTGRES_USER }}:${{ secrets.POSTGRES_PASSWORD }}@localhost:5432/${{ secrets.POSTGRES_DB }}" AWS_ACCESS_KEY_ID: "${{ secrets.GARAGE_KEY_ID }}"
AWS_SECRET_ACCESS_KEY: "${{ secrets.GARAGE_SECRET }}"
AWS_DEFAULT_REGION: "${{ env.AWS_DEFAULT_REGION }}"
GARAGE_DEFAULT_ACCESS_KEY: "${{ secrets.GARAGE_KEY_ID }}"
GARAGE_DEFAULT_SECRET_KEY: "${{ secrets.GARAGE_SECRET }}"
GARAGE_S3_REGION: "${{ env.AWS_DEFAULT_REGION }}"
S3_BUCKET_NAME: ${{ env.S3_BUCKET }}
S3_ENDPOINT_URL: "${{ secrets.S3_CI_ENDPOINT_URL }}"
AWS_S3_FORCE_PATH_STYLE: "true"
run: | run: |
cat .env.sample | head -6 > .env mkdir -p ~/.ssh
cargo test --verbose -- echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/soaricarus_models_deploy_key
chmod 600 ~/.ssh/soaricarus_models_deploy_key
ssh-keyscan ${{ secrets.MYHOST }} >> ~/.ssh/known_hosts
- name: Run clippy eval $(ssh-agent -s)
run: cargo clippy -- -D warnings ssh-add -v ~/.ssh/soaricarus_models_deploy_key
- name: Run rustfmt cargo test
run: cargo fmt --all -- --check
- name: Cleanup
if: always()
run: |
echo "Cleaning up bucket $BUCK"
echo "Region: $AWS_DEFAULT_REGION"
aws --endpoint-url=$ENDPOINT_URL \
s3 rb s3://$BUCK --force \
--region $AWS_DEFAULT_REGION
env:
ENDPOINT_URL: ${{ secrets.S3_CI_ENDPOINT_URL }}
AWS_ACCESS_KEY_ID: "${{ env.AWS_ACCESS_KEY_ID }}"
AWS_SECRET_ACCESS_KEY: "${{ env.AWS_SECRET_ACCESS_KEY }}"
AWS_DEFAULT_REGION: "${{ env.AWS_DEFAULT_REGION }}"
BUCK: ${{ env.S3_BUCKET }}
continue-on-error: true
fmt:
name: Rustfmt
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v6
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: 1.97
- uses: Swatinem/rust-cache@v2
- run: rustup component add rustfmt
- run: |
mkdir -p ~/.ssh
echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/soaricarus_models_deploy_key
chmod 600 ~/.ssh/soaricarus_models_deploy_key
ssh-keyscan ${{ secrets.MYHOST }} >> ~/.ssh/known_hosts
eval $(ssh-agent -s)
ssh-add -v ~/.ssh/soaricarus_models_deploy_key
cargo fmt --all -- --check
clippy:
name: Clippy
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v6
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: 1.97
- uses: Swatinem/rust-cache@v2
- run: rustup component add clippy
- run: |
mkdir -p ~/.ssh
echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/soaricarus_models_deploy_key
chmod 600 ~/.ssh/soaricarus_models_deploy_key
ssh-keyscan ${{ secrets.MYHOST }} >> ~/.ssh/known_hosts
eval $(ssh-agent -s)
ssh-add -v ~/.ssh/soaricarus_models_deploy_key
cargo clippy -- -D warnings
build:
name: build
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v6
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: 1.97
- uses: Swatinem/rust-cache@v2
- run: |
mkdir -p ~/.ssh
echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/soaricarus_models_deploy_key
chmod 600 ~/.ssh/soaricarus_models_deploy_key
ssh-keyscan ${{ secrets.MYHOST }} >> ~/.ssh/known_hosts
eval $(ssh-agent -s)
ssh-add -v ~/.ssh/soaricarus_models_deploy_key
cargo build --release
Generated
+1483 -668
View File
File diff suppressed because it is too large Load Diff
+21 -19
View File
@@ -1,33 +1,35 @@
[package] [package]
name = "icarus" name = "soaricarus_api"
version = "0.4.0" version = "0.5.6"
edition = "2024" edition = "2024"
rust-version = "1.94" rust-version = "1.95"
license = "MIT"
description = "Core API for interacting with soaricarus"
[dependencies] [dependencies]
axum = { version = "0.8.8", features = ["multipart"] } axum = { version = "0.8.9", features = ["multipart"] }
axum-extra = { version = "0.12.5", features = ["cookie"] } axum-extra = { version = "0.12.6", features = ["cookie"] }
serde = { version = "1.0.228", features = ["derive"] } openssl = { version = "0.10.81", features = ["vendored"] }
serde_json = { version = "1.0.149" } serde = { version = "1.0.229", features = ["derive"] }
tower = { version = "0.5.3", features = ["full"] } serde_json = { version = "1.0.151" }
tokio = { version = "1.51", features = ["full"] } tokio = { version = "1.52.3", features = ["full"] }
tokio-util = { version = "0.7.18", features = ["io"] } tokio-util = { version = "0.7.18", features = ["io"] }
tower-http = { version = "0.6.8", features = ["cors", "timeout"] } tower = { version = "0.5.3", features = ["full"] }
tower-http = { version = "0.7.0", features = ["cors", "timeout"] }
tracing-subscriber = "0.3.23" tracing-subscriber = "0.3.23"
futures = { version = "0.3.32" } futures = { version = "0.3.32" }
mime_guess = { version = "2.0.5" } mime_guess = { version = "2.0.5" }
uuid = { version = "1.23", features = ["v4", "serde"] } uuid = { version = "1.24.0", features = ["v4", "serde"] }
sqlx = { version = "0.8.6", features = ["postgres", "runtime-tokio-native-tls", "time", "uuid"] } sqlx = { version = "0.9.0", features = ["postgres", "runtime-tokio", "tls-native-tls", "time", "uuid"] }
time = { version = "0.3.47", features = ["formatting", "macros", "parsing", "serde"] } time = { version = "0.3.54", features = ["formatting", "macros", "parsing", "serde"] }
thiserror = "2.0.18"
base64 = "0.22.1"
jsonwebtoken = { version = "10.3.0", features = ["rust_crypto"] } jsonwebtoken = { version = "10.3.0", features = ["rust_crypto"] }
josekit = { version = "0.10.3" } josekit = { version = "0.10.3" }
utoipa = { version = "5.4.0", features = ["axum_extras"] } utoipa = { version = "5.5.0", features = ["axum_extras"] }
utoipa-swagger-ui = { version = "9.0.2", features = ["axum"] } utoipa-swagger-ui = { version = "9.0.2", features = ["axum"] }
icarus_meta = { git = "ssh://git@git.kundeng.us/phoenix/icarus_meta.git", tag = "v0.5.0" } simeta = { git = "ssh://git@git.kundeng.us/phoenix/simeta.git", tag = "v0.6.1-main-b690995806-680" }
icarus_models = { git = "ssh://git@git.kundeng.us/phoenix/icarus_models.git", tag = "v0.10.0" } simodels = { git = "ssh://git@git.kundeng.us/phoenix/simodels.git", tag = "v0.11.4" }
icarus_envy = { git = "ssh://git@git.kundeng.us/phoenix/icarus_envy.git", tag = "v0.6.0" } sienvy = { git = "ssh://git@git.kundeng.us/phoenix/sienvy.git", tag = "v0.8.0-main-d06c8fdf49-006" }
labyrinth = { git = "ssh://git@git.kundeng.us/phoenix/labyrinth.git", tag = "v0.0.4" }
[dev-dependencies] [dev-dependencies]
common-multipart-rfc7578 = { version = "0.7.0" } common-multipart-rfc7578 = { version = "0.7.0" }
+5 -23
View File
@@ -1,5 +1,4 @@
# Stage 1: Build the application FROM rust:1.97 as builder
FROM rust:1.94 as builder
# Set the working directory inside the container # Set the working directory inside the container
WORKDIR /usr/src/app WORKDIR /usr/src/app
@@ -17,35 +16,25 @@ RUN mkdir -p -m 0700 ~/.ssh && \
echo " User git" >> ~/.ssh/config && \ echo " User git" >> ~/.ssh/config && \
chmod 600 ~/.ssh/config chmod 600 ~/.ssh/config
# << --- ADD HOST KEY HERE --- >>
RUN ssh-keyscan git.kundeng.us >> ~/.ssh/known_hosts RUN ssh-keyscan git.kundeng.us >> ~/.ssh/known_hosts
# Copy Cargo manifests # Copy Cargo manifests
COPY Cargo.toml Cargo.lock ./ COPY Cargo.toml Cargo.lock ./
# Build *only* dependencies to leverage Docker cache
# This dummy build caches dependencies as a separate layer
RUN --mount=type=ssh mkdir src && \ RUN --mount=type=ssh mkdir src && \
echo "fn main() {println!(\"if you see this, the build broke\")}" > src/main.rs && \ echo "fn main() {println!(\"if you see this, the build broke\")}" > src/main.rs && \
cargo build --release --quiet && \ cargo build --release --quiet && \
rm -rf src target/release/deps/icarus* # Clean up dummy build artifacts (replace icarus) rm -rf src target/release/deps/soaricarus_api*
# Copy the actual source code # Copy the actual source code
COPY src ./src COPY src ./src
# If you have other directories like `templates` or `static`, copy them too
COPY .env ./.env COPY .env ./.env
COPY migrations ./migrations COPY migrations ./migrations
# << --- SSH MOUNT ADDED HERE --- >>
# Build *only* dependencies to leverage Docker cache
# This dummy build caches dependencies as a separate layer
# Mount the SSH agent socket for this command
RUN --mount=type=ssh \ RUN --mount=type=ssh \
cargo build --release --quiet cargo build --release --quiet
# Stage 2: Create the final, smaller runtime image FROM debian:trixie-slim
# Use a minimal base image like debian-slim or even distroless for security/size
FROM ubuntu:24.04
# Install runtime dependencies if needed (e.g., SSL certificates) # Install runtime dependencies if needed (e.g., SSL certificates)
RUN apt-get update && apt-get install -y ca-certificates libssl-dev libssl3 && rm -rf /var/lib/apt/lists/* RUN apt-get update && apt-get install -y ca-certificates libssl-dev libssl3 && rm -rf /var/lib/apt/lists/*
@@ -53,18 +42,11 @@ RUN apt-get update && apt-get install -y ca-certificates libssl-dev libssl3 && r
# Set the working directory # Set the working directory
WORKDIR /usr/local/bin WORKDIR /usr/local/bin
# Copy the compiled binary from the builder stage COPY --from=builder /usr/src/app/target/release/soaricarus_api .
# Replace 'icarus' with the actual name of your binary (usually the crate name)
COPY --from=builder /usr/src/app/target/release/icarus .
# Copy other necessary files like .env (if used for runtime config) or static assets
# It's generally better to configure via environment variables in Docker though
COPY --from=builder /usr/src/app/.env . COPY --from=builder /usr/src/app/.env .
COPY --from=builder /usr/src/app/migrations ./migrations COPY --from=builder /usr/src/app/migrations ./migrations
# Expose the port your Axum app listens on (e.g., 3000 or 8000)
EXPOSE 8000 EXPOSE 8000
# Set the command to run your application CMD ["./soaricarus_api"]
# Ensure this matches the binary name copied above
CMD ["./icarus"]
+1 -1
View File
@@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2025 Kun Deng Copyright (c) 2026 Kun Deng
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
+22 -7
View File
@@ -1,24 +1,39 @@
# Icarus # soaricarus_api
Web API for the Icarus project. Core web API for the soaricarus project.
### Requires ### Requires
`icarus_auth` v0.7.x `soaricarus_auth` v0.8.x
`songparser` v0.5.x `songparser` v0.6.x
### Compatible with ### Compatible with
`icarus-dm` v0.9.x `sidm` v0.10.x
## Getting Started ## Getting Started
Quickest way to get started is with docker. Make sure `icarus_auth` and `songparser` repositories Quickest way to get started is with docker. Make sure `soaricarus_auth` and `songparser` repositories
are located in the root of the parent directory. Check the respective repositories to ensure they are located in the root of the parent directory. Check the respective repositories to ensure they
are setup correctly before configuring `Icarus`. are setup correctly before configuring `soaricarus_api`.
Copy the `.env.docker.sample` file to `.env`. Ensure that the `ROOT_DIRECTORY` variable is populated Copy the `.env.docker.sample` file to `.env`. Ensure that the `ROOT_DIRECTORY` variable is populated
and exists on the docker image's filesystem. The credentials for the database doesn't need to be and exists on the docker image's filesystem. The credentials for the database doesn't need to be
changed for development, but if deploying it, it should be modified. changed for development, but if deploying it, it should be modified.
### S3 Setup
Using the copied `.env.docker.sample`, ensure that the following has been properly populated.
```
S3_ENDPOINT_URL - URL of the S3 bucket
S3_BUCKET_NAME - Name of the S3 bucket
GARAGE_S3_REGION - Region of the S3 bucket
GARAGE_RPC_SECRET - A 32-bit hex string
GARAGE_DEFAULT_ACCESS_KEY - Access key starting with GK followed with a 16-bit hex string
GARAGE_DEFAULT_SECRET_KEY - Secret key with a 32-bit hex string
```
The other S3-related environment variables can be left as is for the sake of getting
started.
Build containers Build containers
``` ```
docker compose build --ssh default docker compose build --ssh default
+103 -36
View File
@@ -3,42 +3,44 @@ version: '3.8' # Use a recent version
services: services:
# --- Web API --- # --- Web API ---
api: api:
build: # Tells docker-compose to build the Dockerfile in the current directory build:
context: . context: .
ssh: ["default"] # Uses host's SSH agent ssh: ["default"]
container_name: icarus # Optional: Give the container a specific name container_name: api
ports: ports:
# Map host port 8000 to container port 3000 (adjust as needed)
- "8000:8000" - "8000:8000"
env_file: env_file:
- .env - .env
volumes:
- shared_credentials:/shared:ro # Mount as read-only
depends_on: depends_on:
main_db: main_db:
condition: service_healthy condition: service_healthy
maze:
condition: service_started
auth_api:
condition: service_started
networks: networks:
- icarus-network - soaricarus-network
restart: unless-stopped # Optional: Restart policy restart: unless-stopped
# --- Web API auth --- # --- Web API auth ---
auth_api: auth_api:
build: build:
context: ../icarus_auth # IMPORTANT: Relative path to the local checkout of your web API repo (containing the Dockerfile) context: ../soaricarus_auth # IMPORTANT: Relative path to the local checkout of your web API repo (containing the Dockerfile)
ssh: ["default"] # Uses host's SSH agent ssh: ["default"]
dockerfile: Dockerfile # Optional: Specify if your Dockerfile has a non-standard name dockerfile: Dockerfile # Optional: Specify if your Dockerfile has a non-standard name
container_name: auth_api container_name: auth_api
restart: unless-stopped restart: unless-stopped
ports: ports:
- "8001:8001" # Map host port 8000 to container port 8000 (adjust container port based on your app's EXPOSE in Dockerfile) - "8001:8001"
# environment:
# Environment variables your API needs, e.g., database connection
# Add other necessary environment variables
env_file: env_file:
- ../icarus_auth/.env - ../soaricarus_auth/.env
depends_on: depends_on:
auth_db: auth_db:
condition: service_healthy condition: service_healthy
networks: networks:
- icarus-network - soaricarus-network
# Optional: Mount local code for development (live reload) # Optional: Mount local code for development (live reload)
# volumes: # volumes:
# - ./path/to/your/web-api-repo:/app # - ./path/to/your/web-api-repo:/app
@@ -58,25 +60,89 @@ services:
- main_db - main_db
- auth_api - auth_api
- auth_db - auth_db
- maze
networks: networks:
- icarus-network - soaricarus-network
# --- storage service ---
maze:
image: dxflrs/garage:v2.3.0 # Use official Garage image
container_name: maze
restart: unless-stopped
environment:
# Garage configuration via environment variables
GARAGE_SERVER_BIND_ADDR: "0.0.0.0:3900"
GARAGE_SERVER_BIND_PUBLIC_ADDR: "0.0.0.0:3901"
GARAGE_S3_API_BIND_ADDR: "0.0.0.0:3900"
GARAGE_S3_API_PUBLIC_BIND_ADDR: "0.0.0.0:3901"
GARAGE_S3_API_PUBLIC_ADDRS: "http://localhost:3901"
GARAGE_S3_API_ROOT_DOMAIN: "${GARAGE_ROOT_DOMAIN}"
GARAGE_ADMIN_BIND_ADDR: "0.0.0.0:3902"
GARAGE_ADMIN_PUBLIC_BIND_ADDR: "0.0.0.0:3902"
# Cluster configuration (single node for simplicity)
GARAGE_CLUSTER_REPLICATION_MODE: "none" # Single node
GARAGE_CLUSTER_PEER_REPLICATION_STRATEGY: "ring"
# Data storage
GARAGE_DATA_DIR: "/data"
GARAGE_METADATA_DIR: "/data/meta"
# RPC configuration
GARAGE_RPC_PUBLIC_ADDR: "http://maze:3901"
GARAGE_RPC_BIND_ADDR: "0.0.0.0:3901"
GARAGE_RPC_SECRET: "${GARAGE_RPC_SECRET}" # 32+ chars
# Web Admin
GARAGE_ADMIN_TOKEN: "${GARAGE_ADMIN_TOKEN}"
# Logging
GARAGE_LOG_LEVEL: "info"
GARAGE_S3_BUCKET: "${S3_BUCKET_NAME}"
GARAGE_S3_REGION: "${GARAGE_S3_REGION}"
GARAGE_DEFAULT_ACCESS_KEY: "${GARAGE_DEFAULT_ACCESS_KEY}"
GARAGE_DEFAULT_SECRET_KEY: "${GARAGE_DEFAULT_SECRET_KEY}"
GARAGE_DEFAULT_BUCKET: "${GARAGE_DEFAULT_BUCKET}"
AWS_ACCESS_KEY_ID: "${GARAGE_DEFAULT_ACCESS_KEY}"
AWS_SECRET_ACCESS_KEY: "${GARAGE_DEFAULT_SECRET_KEY}"
AWS_DEFAULT_REGION: "${GARAGE_DEFAULT_BUCKET}"
AWS_S3_FORCE_PATH_STYLE: "true"
volumes:
- ./garage.toml:/etc/garage.toml:ro # Mount config file
- shared_credentials:/data # Persist Garage data
ports:
- "3900:3900" # S3 API
- "3901:3901" # S3 API public
- "3902:3902" # Admin API
command: ["/garage", "server", "--single-node", "--default-bucket"]
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3900/"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
networks:
- soaricarus-network
extra_hosts:
- "garage.local:127.0.0.1"
# PostgreSQL Database Service # PostgreSQL Database Service
# --- icarus web api db ---
main_db: main_db:
image: postgres:18.3-alpine # Use an official Postgres image (Alpine variant is smaller) image: postgres:18.4-alpine
container_name: icarus_db # Optional: Give the container a specific name container_name: soaricarus_db
environment: environment:
# These MUST match the user, password, and database name in the DATABASE_URL above # These MUST match the user, password, and database name in the DATABASE_URL above
POSTGRES_USER: ${POSTGRES_MAIN_USER:-icarus} POSTGRES_USER: ${POSTGRES_MAIN_USER:-soaricarus}
POSTGRES_PASSWORD: ${POSTGRES_MAIN_PASSWORD:-password} POSTGRES_PASSWORD: ${POSTGRES_MAIN_PASSWORD:-password}
POSTGRES_DB: ${POSTGRES_MAIN_DB:-icarus_db} POSTGRES_DB: ${POSTGRES_MAIN_DB:-soaricarus_db}
volumes: volumes:
# Persist database data using a named volume # Persist database data using a named volume
- postgres_data:/var/lib/postgresql/data - postgres_data:/var/lib/postgresql
ports: ports:
# Optional: Expose port 5432 ONLY if you need to connect directly from your host machine (e.g., for debugging)
- "5432:5432" - "5432:5432"
healthcheck: healthcheck:
# Checks if Postgres is ready to accept connections # Checks if Postgres is ready to accept connections
@@ -85,24 +151,23 @@ services:
timeout: 5s timeout: 5s
retries: 5 retries: 5
start_period: 10s start_period: 10s
restart: always # Optional: Restart policy restart: always
networks: networks:
- icarus-network - soaricarus-network
# --- icarus web auth api db --- # --- soaricarus web auth api db ---
auth_db: auth_db:
image: postgres:18.3-alpine # Use an official Postgres image (Alpine variant is smaller) image: postgres:18.4-alpine
container_name: icarus_auth_db # Optional: Give the container a specific name container_name: soaricarus_auth_db
environment: environment:
# These MUST match the user, password, and database name in the DATABASE_URL above # These MUST match the user, password, and database name in the DATABASE_URL above
POSTGRES_USER: ${POSTGRES_AUTH_USER:-icarus_op} POSTGRES_USER: ${POSTGRES_AUTH_USER:-soaricarus_op}
POSTGRES_PASSWORD: ${POSTGRES_AUTH_PASSWORD:-password} POSTGRES_PASSWORD: ${POSTGRES_AUTH_PASSWORD:-password}
POSTGRES_DB: ${POSTGRES_AUTH_DB:-icarus_auth_db} POSTGRES_DB: ${POSTGRES_AUTH_DB:-soaricarus_auth_db}
volumes: volumes:
# Persist database data using a named volume # Persist database data using a named volume
- postgres_data_auth:/var/lib/postgresql/data - postgres_data_auth:/var/lib/postgresql
ports: ports:
# Optional: Expose port 5432 ONLY if you need to connect directly from your host machine (e.g., for debugging)
- "5433:5432" - "5433:5432"
healthcheck: healthcheck:
# Checks if Postgres is ready to accept connections # Checks if Postgres is ready to accept connections
@@ -111,18 +176,20 @@ services:
timeout: 5s timeout: 5s
retries: 5 retries: 5
start_period: 10s start_period: 10s
restart: always # Optional: Restart policy restart: always
networks: networks:
- icarus-network - soaricarus-network
# Define the named volume for data persistence
volumes: volumes:
postgres_data: postgres_data:
driver: local # Use the default local driver driver: local # Use the default local driver
postgres_data_auth: postgres_data_auth:
driver: local # Use the default local driver driver: local # Use the default local driver
object_data:
driver: local # Use the default local driver
shared_credentials: # New shared volume
driver: local
# Define the network (optional, but good practice)
networks: networks:
icarus-network: soaricarus-network:
driver: bridge driver: bridge
+24
View File
@@ -0,0 +1,24 @@
metadata_dir = "/var/lib/garage/meta"
data_dir = "/var/lib/garage/data"
db_engine = "sqlite"
replication_factor = 1
rpc_public_addr = "127.0.0.1:3901"
rpc_secret = "${GARAGE_RPC_SECRET}"
rpc_bind_addr = "[::]:3901"
[s3_api]
s3_region = "maze"
api_bind_addr = "[::]:3900"
root_domain = ".s3.maze.localhost"
[s3_web]
bind_addr = "[::]:3902"
root_domain = ".web.maze.localhost"
index = "index.html"
[admin]
api_bind_addr = "[::]:3903"
admin_token = "${GARAGE_ADMIN_TOKEN}"
metrics_token = "${GARAGE_METRICS_TOKEN}"
+17 -2
View File
@@ -6,10 +6,17 @@ CREATE TABLE IF NOT EXISTS "songQueue" (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
filename TEXT NOT NULL, filename TEXT NOT NULL,
status TEXT CHECK (status IN ('pending', 'ready', 'processing', 'done')), status TEXT CHECK (status IN ('pending', 'ready', 'processing', 'done')),
data BYTEA NULL,
user_id UUID NULL user_id UUID NULL
); );
CREATE TABLE IF NOT EXISTS "songQueueData" (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
file_key TEXT NOT NULL,
bucket TEXT NOT NULL,
region TEXT NOT NULL,
song_queue_id UUID NOT NULL
);
-- Table to store queued metadata -- Table to store queued metadata
CREATE TABLE IF NOT EXISTS "metadataQueue" ( CREATE TABLE IF NOT EXISTS "metadataQueue" (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
@@ -21,11 +28,18 @@ CREATE TABLE IF NOT EXISTS "metadataQueue" (
-- Table to store queued coverart -- Table to store queued coverart
CREATE TABLE IF NOT EXISTS "coverartQueue" ( CREATE TABLE IF NOT EXISTS "coverartQueue" (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
data BYTEA NULL,
file_type TEXT NOT NULL, file_type TEXT NOT NULL,
song_queue_id UUID NULL song_queue_id UUID NULL
); );
CREATE TABLE IF NOT EXISTS "coverartQueueData" (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
file_key TEXT NOT NULL,
bucket TEXT NOT NULL,
region TEXT NOT NULL,
coverart_queue_id UUID NOT NULL
);
-- Create an index for better query performance -- Create an index for better query performance
CREATE INDEX metadata_queue_data_metadata ON "metadataQueue" USING gin (metadata); CREATE INDEX metadata_queue_data_metadata ON "metadataQueue" USING gin (metadata);
@@ -49,6 +63,7 @@ CREATE TABLE IF NOT EXISTS "song" (
date_created timestamptz DEFAULT now(), date_created timestamptz DEFAULT now(),
filename TEXT NOT NULL, filename TEXT NOT NULL,
directory TEXT NOT NULL, directory TEXT NOT NULL,
file_key TEXT NOT NULL,
user_id UUID NULL user_id UUID NULL
-- TODO: Add coverart id later. This will allow multiple songs to be linked to a single cover art -- TODO: Add coverart id later. This will allow multiple songs to be linked to a single cover art
); );
+3 -3
View File
@@ -36,11 +36,11 @@ pub async fn auth<B>(
(StatusCode::UNAUTHORIZED, Json(json_error)) (StatusCode::UNAUTHORIZED, Json(json_error))
})?; })?;
let secret_key = icarus_envy::environment::get_secret_main_key().await.value; let secret_key = sienvy::environment::get_secret_main_key().value;
let mut validation = Validation::new(jsonwebtoken::Algorithm::HS256); let mut validation = Validation::new(jsonwebtoken::Algorithm::HS256);
validation.set_audience(&["icarus"]); // Must match exactly what's in the token validation.set_audience(&["soaricarus"]);
let _claims = decode::<icarus_models::token::UserClaims>( let _claims = decode::<simodels::token::UserClaims>(
&token, &token,
&DecodingKey::from_secret(secret_key.as_ref()), &DecodingKey::from_secret(secret_key.as_ref()),
&validation, &validation,
+84 -35
View File
@@ -20,7 +20,7 @@ pub mod response {
#[derive(Debug, Default, serde::Deserialize, serde::Serialize, utoipa::ToSchema)] #[derive(Debug, Default, serde::Deserialize, serde::Serialize, utoipa::ToSchema)]
pub struct Response { pub struct Response {
pub message: String, pub message: String,
pub data: Vec<icarus_models::coverart::CoverArt>, pub data: Vec<simodels::coverart::CoverArt>,
} }
} }
@@ -28,7 +28,7 @@ pub mod response {
#[derive(Debug, Default, serde::Deserialize, serde::Serialize, utoipa::ToSchema)] #[derive(Debug, Default, serde::Deserialize, serde::Serialize, utoipa::ToSchema)]
pub struct Response { pub struct Response {
pub message: String, pub message: String,
pub data: Vec<icarus_models::coverart::CoverArt>, pub data: Vec<simodels::coverart::CoverArt>,
} }
} }
} }
@@ -63,26 +63,28 @@ pub mod endpoint {
let mut response = super::response::create_coverart::Response::default(); let mut response = super::response::create_coverart::Response::default();
let id = payload.coverart_queue_id; let id = payload.coverart_queue_id;
match repo_queue::coverart::get_coverart_queue_data_with_id(&pool, &id).await { match repo_queue::data::queue::coverart::get_with_coverart_queue_id(&pool, &id).await {
Ok((_id, file_key, _bucket, _region, _)) => {
let lab_config = crate::util::maze::get_config();
let lr = labyrinth::Labyrinth { config: lab_config };
match lr.download(&file_key).await {
Ok(data) => { Ok(data) => {
let song_id = payload.song_id;
match crate::repo::song::get_song(&pool, &song_id).await {
Ok(song) => {
let directory = icarus_envy::environment::get_root_directory().await.value;
let file_type = let file_type =
icarus_meta::detection::coverart::file_type_from_data(&data).unwrap(); simeta::detection::coverart::file_type_from_data(&data).unwrap();
let coverart_type = if file_type.file_type let coverart_type = if file_type.file_type
== icarus_meta::detection::coverart::constants::JPEG_TYPE == simeta::detection::coverart::constants::JPEG_TYPE
{ {
icarus_models::types::CoverArtType::JpegExtension simodels::types::CoverArtType::JpegExtension
} else if file_type.file_type } else if file_type.file_type
== icarus_meta::detection::coverart::constants::JPG_TYPE == simeta::detection::coverart::constants::JPG_TYPE
{ {
icarus_models::types::CoverArtType::JpgExtension simodels::types::CoverArtType::JpgExtension
} else if file_type.file_type } else if file_type.file_type
== icarus_meta::detection::coverart::constants::PNG_TYPE == simeta::detection::coverart::constants::PNG_TYPE
{ {
icarus_models::types::CoverArtType::PngExtension simodels::types::CoverArtType::PngExtension
} else { } else {
response.message = String::from("Invalid CoverArt type"); response.message = String::from("Invalid CoverArt type");
return ( return (
@@ -91,22 +93,52 @@ pub mod endpoint {
); );
}; };
let filename = let filename =
icarus_models::coverart::generate_filename(coverart_type, true) simodels::coverart::generate_filename(coverart_type, true).unwrap();
.unwrap();
let data = labyrinth::Data {
raw_data: data,
..Default::default()
};
let new_file_key = format!("processed/song/{filename}");
match lr.upload(&new_file_key, &data).await {
Ok(_resp) => {
// TODO: Change it to the s3 bucket
let directory = sienvy::environment::get_root_directory().value;
let mut coverart = let mut coverart =
icarus_models::coverart::init::init_coverart_dir_and_filename( simodels::coverart::init::init_coverart_dir_and_filename(
&directory, &filename, &directory, &filename,
); );
let song = match crate::repo::song::get_song(
&pool,
&payload.song_id,
)
.await
{
Ok(song) => song,
Err(err) => {
eprintln!("Error: {err:?}");
return (
axum::http::StatusCode::BAD_REQUEST,
axum::Json(response),
);
}
};
coverart.title = song.album.clone(); coverart.title = song.album.clone();
coverart.file_type = file_type.file_type; coverart.file_type = file_type.file_type;
coverart.data = data; coverart.data = data.raw_data;
match coverart.save_to_filesystem() { match coverart.save_to_filesystem() {
Ok(_) => { Ok(_) => {
match repo::coverart::create(&pool, &coverart, &song.id).await { match repo::coverart::create(
&pool,
&coverart,
&payload.song_id,
)
.await
{
Ok(id) => { Ok(id) => {
coverart.song_id = song_id; coverart.song_id = payload.song_id;
coverart.id = id; coverart.id = id;
println!("Cover Art created"); println!("Cover Art created");
@@ -117,7 +149,10 @@ pub mod endpoint {
} }
Err(err) => { Err(err) => {
response.message = err.to_string(); response.message = err.to_string();
(axum::http::StatusCode::BAD_REQUEST, axum::Json(response)) (
axum::http::StatusCode::BAD_REQUEST,
axum::Json(response),
)
} }
} }
} }
@@ -131,14 +166,29 @@ pub mod endpoint {
} }
} }
Err(err) => { Err(err) => {
response.message = err.to_string(); eprintln!("Error: {err:?}");
(axum::http::StatusCode::BAD_REQUEST, axum::Json(response)) (
axum::http::StatusCode::INTERNAL_SERVER_ERROR,
axum::Json(response),
)
} }
} }
} }
Err(err) => { Err(err) => {
response.message = err.to_string(); eprintln!("Error: {err:?}");
(axum::http::StatusCode::BAD_REQUEST, axum::Json(response)) (
axum::http::StatusCode::INTERNAL_SERVER_ERROR,
axum::Json(response),
)
}
}
}
Err(err) => {
eprintln!("Error: {err:?}");
(
axum::http::StatusCode::INTERNAL_SERVER_ERROR,
axum::Json(response),
)
} }
} }
} }
@@ -200,23 +250,23 @@ pub mod endpoint {
axum::extract::Path(id): axum::extract::Path<uuid::Uuid>, axum::extract::Path(id): axum::extract::Path<uuid::Uuid>,
) -> (axum::http::StatusCode, axum::response::Response) { ) -> (axum::http::StatusCode, axum::response::Response) {
match repo::coverart::get_coverart(&pool, &id).await { match repo::coverart::get_coverart(&pool, &id).await {
Ok(coverart) => match icarus_models::coverart::io::to_data(&coverart) { Ok(coverart) => match simodels::coverart::io::to_data(&coverart) {
Ok(data) => { Ok(data) => {
let (file_type, img_type) = let (file_type, img_type) =
match icarus_meta::detection::coverart::file_type_from_data(&data) { match simeta::detection::coverart::file_type_from_data(&data) {
Ok(file_type) => { Ok(file_type) => {
if file_type.file_type if file_type.file_type
== icarus_meta::detection::coverart::constants::JPEG_TYPE == simeta::detection::coverart::constants::JPEG_TYPE
{ {
(file_type, icarus_models::types::CoverArtType::JpegExtension) (file_type, simodels::types::CoverArtType::JpegExtension)
} else if file_type.file_type } else if file_type.file_type
== icarus_meta::detection::coverart::constants::JPG_TYPE == simeta::detection::coverart::constants::JPG_TYPE
{ {
(file_type, icarus_models::types::CoverArtType::JpgExtension) (file_type, simodels::types::CoverArtType::JpgExtension)
} else if file_type.file_type } else if file_type.file_type
== icarus_meta::detection::coverart::constants::PNG_TYPE == simeta::detection::coverart::constants::PNG_TYPE
{ {
(file_type, icarus_models::types::CoverArtType::PngExtension) (file_type, simodels::types::CoverArtType::PngExtension)
} else { } else {
return ( return (
axum::http::StatusCode::INTERNAL_SERVER_ERROR, axum::http::StatusCode::INTERNAL_SERVER_ERROR,
@@ -235,8 +285,7 @@ pub mod endpoint {
let bytes = axum::body::Bytes::from(data); let bytes = axum::body::Bytes::from(data);
let mut response = bytes.into_response(); let mut response = bytes.into_response();
let headers = response.headers_mut(); let headers = response.headers_mut();
let filename = let filename = simodels::coverart::generate_filename(img_type, true).unwrap();
icarus_models::coverart::generate_filename(img_type, true).unwrap();
headers.insert( headers.insert(
axum::http::header::CONTENT_TYPE, axum::http::header::CONTENT_TYPE,
file_type.mime.parse().unwrap(), file_type.mime.parse().unwrap(),
+112 -24
View File
@@ -102,9 +102,9 @@ pub mod response {
mod helper { mod helper {
pub fn is_coverart_file_type_valid(file_type: &String) -> bool { pub fn is_coverart_file_type_valid(file_type: &String) -> bool {
let valid_file_types = vec![ let valid_file_types = vec![
String::from(icarus_meta::detection::coverart::constants::JPEG_TYPE), String::from(simeta::detection::coverart::constants::JPEG_TYPE),
String::from(icarus_meta::detection::coverart::constants::JPG_TYPE), String::from(simeta::detection::coverart::constants::JPG_TYPE),
String::from(icarus_meta::detection::coverart::constants::PNG_TYPE), String::from(simeta::detection::coverart::constants::PNG_TYPE),
]; ];
for valid_file_type in valid_file_types { for valid_file_type in valid_file_types {
@@ -150,8 +150,7 @@ pub mod endpoint {
let content_type = field.content_type().unwrap().to_string(); let content_type = field.content_type().unwrap().to_string();
let data = field.bytes().await.unwrap(); let data = field.bytes().await.unwrap();
let raw_data = data.to_vec(); let raw_data = data.to_vec();
let file_type = let file_type = match simeta::detection::coverart::file_type_from_data(&raw_data) {
match icarus_meta::detection::coverart::file_type_from_data(&raw_data) {
Ok(file_type) => file_type, Ok(file_type) => file_type,
Err(err) => { Err(err) => {
eprintln!("Error: {err:?}"); eprintln!("Error: {err:?}");
@@ -179,14 +178,60 @@ pub mod endpoint {
file_type file_type
); );
match repo::coverart::insert(&pool, &raw_data, &file_type.file_type).await { let lab_config = crate::util::maze::get_config();
let lr = labyrinth::Labyrinth { config: lab_config };
let data = labyrinth::Data {
raw_data,
..Default::default()
};
let filename = simodels::coverart::generate_filename(
simodels::types::CoverArtType::JpegExtension,
true,
)
.unwrap();
let file_path = format!("queued/coverart/{filename}");
println!("Key: {file_path:?}");
match lr.upload(&file_path, &data).await {
Ok(_res) => {
match repo::coverart::insert(&pool, &file_type.file_type).await {
Ok(id) => { Ok(id) => {
println!("Inserting queued data");
match repo::data::queue::coverart::insert(
&pool,
&file_path,
&lr.config.bucket,
&lr.config.region,
&id,
)
.await
{
Ok(_id) => {
println!("Successfully did it");
response.message = String::from("Successful"); response.message = String::from("Successful");
response.data.push(id); response.data.push(id);
(axum::http::StatusCode::OK, axum::Json(response)) (axum::http::StatusCode::OK, axum::Json(response))
} }
Err(err) => { Err(err) => {
response.message = err.to_string(); response.message = err.to_string();
(
axum::http::StatusCode::BAD_REQUEST,
axum::Json(response),
)
}
}
}
Err(err) => {
response.message = err.to_string();
(axum::http::StatusCode::BAD_REQUEST, axum::Json(response))
}
}
}
Err(err) => {
eprintln!("Error: {err:?}");
(axum::http::StatusCode::BAD_REQUEST, axum::Json(response)) (axum::http::StatusCode::BAD_REQUEST, axum::Json(response))
} }
} }
@@ -314,12 +359,19 @@ pub mod endpoint {
)] )]
pub async fn fetch_coverart_with_data( pub async fn fetch_coverart_with_data(
axum::Extension(pool): axum::Extension<sqlx::PgPool>, axum::Extension(pool): axum::Extension<sqlx::PgPool>,
axum::extract::Path(id): axum::extract::Path<uuid::Uuid>, axum::extract::Path(coverart_queue_id): axum::extract::Path<uuid::Uuid>,
) -> (axum::http::StatusCode, axum::response::Response) { ) -> (axum::http::StatusCode, axum::response::Response) {
match repo::coverart::get_coverart_queue_data_with_id(&pool, &id).await { match repo::data::queue::coverart::get_with_coverart_queue_id(&pool, &coverart_queue_id)
.await
{
Ok((_id, file_key, _bucket, _region, _)) => {
let lab_config = crate::util::maze::get_config();
let lr = labyrinth::Labyrinth { config: lab_config };
match lr.download(&file_key).await {
Ok(data) => { Ok(data) => {
let file_type = let file_type =
icarus_meta::detection::coverart::file_type_from_data(&data).unwrap(); simeta::detection::coverart::file_type_from_data(&data).unwrap();
let bytes = axum::body::Bytes::from(data); let bytes = axum::body::Bytes::from(data);
let mut response = bytes.into_response(); let mut response = bytes.into_response();
let headers = response.headers_mut(); let headers = response.headers_mut();
@@ -329,17 +381,17 @@ pub mod endpoint {
); );
let coverart_type = if file_type.file_type let coverart_type = if file_type.file_type
== icarus_meta::detection::coverart::constants::JPEG_TYPE == simeta::detection::coverart::constants::JPEG_TYPE
{ {
icarus_models::types::CoverArtType::JpegExtension simodels::types::CoverArtType::JpegExtension
} else if file_type.file_type } else if file_type.file_type
== icarus_meta::detection::coverart::constants::JPG_TYPE == simeta::detection::coverart::constants::JPG_TYPE
{ {
icarus_models::types::CoverArtType::JpgExtension simodels::types::CoverArtType::JpgExtension
} else if file_type.file_type } else if file_type.file_type
== icarus_meta::detection::coverart::constants::PNG_TYPE == simeta::detection::coverart::constants::PNG_TYPE
{ {
icarus_models::types::CoverArtType::PngExtension simodels::types::CoverArtType::PngExtension
} else { } else {
return ( return (
axum::http::StatusCode::INTERNAL_SERVER_ERROR, axum::http::StatusCode::INTERNAL_SERVER_ERROR,
@@ -347,7 +399,7 @@ pub mod endpoint {
); );
}; };
let filename = let filename =
icarus_models::coverart::generate_filename(coverart_type, true).unwrap(); simodels::coverart::generate_filename(coverart_type, true).unwrap();
headers.insert( headers.insert(
axum::http::header::CONTENT_DISPOSITION, axum::http::header::CONTENT_DISPOSITION,
format!("attachment; filename=\"{filename}\"") format!("attachment; filename=\"{filename}\"")
@@ -357,10 +409,23 @@ pub mod endpoint {
(axum::http::StatusCode::OK, response) (axum::http::StatusCode::OK, response)
} }
Err(_err) => ( Err(err) => {
axum::http::StatusCode::BAD_REQUEST, eprintln!("Error: {err:?}");
(
axum::http::StatusCode::INTERNAL_SERVER_ERROR,
axum::response::Response::default(), axum::response::Response::default(),
), )
}
}
}
Err(err) => {
eprintln!("Record not found");
eprintln!("Error: {err:?}");
(
axum::http::StatusCode::INTERNAL_SERVER_ERROR,
axum::response::Response::default(),
)
}
} }
} }
@@ -390,16 +455,39 @@ pub mod endpoint {
let coverart_queue_id = payload.coverart_queue_id; let coverart_queue_id = payload.coverart_queue_id;
match repo::coverart::get_coverart_queue_with_id(&pool, &coverart_queue_id).await { match repo::coverart::get_coverart_queue_with_id(&pool, &coverart_queue_id).await {
Ok(coverart_queue) => { Ok(_coverart_queue) => {
match repo::coverart::wipe_data(&pool, &coverart_queue.id).await { match repo::data::queue::coverart::get_with_coverart_queue_id(
Ok(id) => { &pool,
&coverart_queue_id,
)
.await
{
Ok((_id, file_key, _bucket, _region, _)) => {
let lab_config = crate::util::maze::get_config();
let lr = labyrinth::Labyrinth { config: lab_config };
match lr.delete(&file_key).await {
Ok(_) => {
response.message = String::from("Success"); response.message = String::from("Success");
response.data.push(id); response.data.push(coverart_queue_id);
(axum::http::StatusCode::OK, axum::Json(response)) (axum::http::StatusCode::OK, axum::Json(response))
} }
Err(err) => {
eprintln!("Error: {err:?}");
(
axum::http::StatusCode::INTERNAL_SERVER_ERROR,
axum::Json(response),
)
}
}
}
Err(err) => { Err(err) => {
response.message = err.to_string(); response.message = err.to_string();
(axum::http::StatusCode::BAD_REQUEST, axum::Json(response)) (
axum::http::StatusCode::INTERNAL_SERVER_ERROR,
axum::Json(response),
)
} }
} }
} }
+152 -18
View File
@@ -97,9 +97,9 @@ pub mod response {
} }
pub async fn is_song_valid(data: &[u8]) -> Result<bool, std::io::Error> { pub async fn is_song_valid(data: &[u8]) -> Result<bool, std::io::Error> {
match icarus_meta::detection::song::file_type_from_data(data) { match simeta::detection::song::file_type_from_data(data) {
Ok(file_type) => { Ok(file_type) => {
if file_type.file_type == icarus_meta::detection::song::constants::FLAC_TYPE { if file_type.file_type == simeta::detection::song::constants::FLAC_TYPE {
Ok(true) Ok(true)
} else { } else {
Ok(false) Ok(false)
@@ -156,12 +156,12 @@ pub mod endpoint {
); );
let raw_data: Vec<u8> = data.to_vec(); let raw_data: Vec<u8> = data.to_vec();
let copied_raw_data = data.to_vec();
match super::is_song_valid(&raw_data).await { match super::is_song_valid(&raw_data).await {
Ok(valid) => { Ok(valid) => {
if valid { if valid {
match repo::song::insert( match repo::song::insert(
&pool, &pool,
&raw_data,
&file_name, &file_name,
&crate::repo::queue::song::status::PENDING.to_string(), &crate::repo::queue::song::status::PENDING.to_string(),
) )
@@ -169,6 +169,51 @@ pub mod endpoint {
{ {
Ok(queued_song) => { Ok(queued_song) => {
results.push(queued_song); results.push(queued_song);
println!("Uploading to bucket");
let lab_config = crate::util::maze::get_config();
let lr = labyrinth::Labyrinth { config: lab_config };
let data = labyrinth::Data {
raw_data: copied_raw_data,
..Default::default()
};
println!("Filename: {file_name:?}");
let file_path = format!("queued/song/{file_name}");
println!("Path: {file_path:?}");
match lr.upload(&file_path, &data).await {
Ok(_res) => {
match repo::data::insert(
&pool,
&file_path,
&lr.config.bucket,
&lr.config.region,
&queued_song,
)
.await
{
Ok(_id) => {}
Err(err) => {
eprintln!("Error: {err:?}");
return (axum::http::StatusCode::INTERNAL_SERVER_ERROR, axum::Json(response));
}
}
}
Err(err) => match err {
labyrinth::Error::Info(err_str) => {
eprintln!("Error: {:?}", err_str);
response.message = err_str.to_string();
return (axum::http::StatusCode::INTERNAL_SERVER_ERROR, axum::Json(response));
}
labyrinth::Error::SError(err_s) => {
eprintln!("Error: {:?}", err_s);
response.message = err_s.to_string();
return (axum::http::StatusCode::INTERNAL_SERVER_ERROR, axum::Json(response));
}
},
}
} }
Err(err) => { Err(err) => {
response.message = err.to_string(); response.message = err.to_string();
@@ -297,11 +342,15 @@ pub mod endpoint {
)] )]
pub async fn download_queued_song( pub async fn download_queued_song(
axum::Extension(pool): axum::Extension<sqlx::PgPool>, axum::Extension(pool): axum::Extension<sqlx::PgPool>,
axum::extract::Path(id): axum::extract::Path<uuid::Uuid>, axum::extract::Path(song_queue_id): axum::extract::Path<uuid::Uuid>,
) -> (axum::http::StatusCode, axum::response::Response) { ) -> (axum::http::StatusCode, axum::response::Response) {
println!("Id: {id}"); println!("Id: {song_queue_id}");
match repo::song::get_data(&pool, &id).await { let lab_config = crate::util::maze::get_config();
let lr = labyrinth::Labyrinth { config: lab_config };
match repo::data::get_with_song_queue_id(&pool, &song_queue_id).await {
Ok((id, file_key, _bucket, _region, _)) => match lr.download(&file_key).await {
Ok(data) => { Ok(data) => {
let by = axum::body::Bytes::from(data); let by = axum::body::Bytes::from(data);
let mut response = by.into_response(); let mut response = by.into_response();
@@ -319,6 +368,14 @@ pub mod endpoint {
(axum::http::StatusCode::OK, response) (axum::http::StatusCode::OK, response)
} }
Err(err) => {
eprintln!("Error: {err:?}");
(
axum::http::StatusCode::INTERNAL_SERVER_ERROR,
axum::response::Response::default(),
)
}
},
Err(_err) => ( Err(_err) => (
axum::http::StatusCode::BAD_REQUEST, axum::http::StatusCode::BAD_REQUEST,
axum::response::Response::default(), axum::response::Response::default(),
@@ -406,7 +463,7 @@ pub mod endpoint {
) )
)] )]
pub async fn update_song_queue( pub async fn update_song_queue(
axum::extract::Path(id): axum::extract::Path<uuid::Uuid>, axum::extract::Path(song_queue_id): axum::extract::Path<uuid::Uuid>,
axum::Extension(pool): axum::Extension<sqlx::PgPool>, axum::Extension(pool): axum::Extension<sqlx::PgPool>,
mut multipart: axum::extract::Multipart, mut multipart: axum::extract::Multipart,
) -> ( ) -> (
@@ -437,12 +494,45 @@ pub mod endpoint {
match super::is_song_valid(&raw_data).await { match super::is_song_valid(&raw_data).await {
Ok(valid) => { Ok(valid) => {
if valid { if valid {
match repo::song::update(&pool, &raw_data, &id).await { let lab_config = crate::util::maze::get_config();
let lr = labyrinth::Labyrinth { config: lab_config };
println!("Valid song");
match repo::data::get_with_song_queue_id(&pool, &song_queue_id).await {
Ok((id, file_key, _bucket, _region, _)) => {
match lr.delete(&file_key).await {
Ok(_response) => {
let data = labyrinth::Data {
raw_data,
..Default::default()
};
let filename = simodels::song::generate_filename(
simodels::types::MusicType::FlacExtension,
true,
)
.unwrap();
let new_file_key = format!("queued/song/{filename}");
println!("New key: {new_file_key:?}");
match lr.upload(&new_file_key, &data).await {
Ok(_) => {
match repo::data::update_file_key(
&pool,
&id,
&new_file_key,
)
.await
{
Ok(_) => { Ok(_) => {
response.message = response.message =
String::from(super::super::super::response::SUCCESSFUL); super::super::super::response::SUCCESSFUL.to_string();
response.data.push(id); response.data.push(song_queue_id);
(axum::http::StatusCode::OK, axum::Json(response)) (
axum::http::StatusCode::OK,
axum::Json(response),
)
} }
Err(err) => { Err(err) => {
response.message = err.to_string(); response.message = err.to_string();
@@ -452,6 +542,33 @@ pub mod endpoint {
) )
} }
} }
}
Err(err) => {
eprintln!("Error: {err:?}");
(
axum::http::StatusCode::INTERNAL_SERVER_ERROR,
axum::Json(response),
)
}
}
}
Err(err) => {
eprintln!("Error: {err:?}");
(
axum::http::StatusCode::INTERNAL_SERVER_ERROR,
axum::Json(response),
)
}
}
}
Err(err) => {
eprintln!("Error: {err:?}");
(
axum::http::StatusCode::INTERNAL_SERVER_ERROR,
axum::Json(response),
)
}
}
} else { } else {
response.message = String::from("Invalid song type"); response.message = String::from("Invalid song type");
(axum::http::StatusCode::BAD_REQUEST, axum::Json(response)) (axum::http::StatusCode::BAD_REQUEST, axum::Json(response))
@@ -482,7 +599,8 @@ pub mod endpoint {
), ),
responses( responses(
(status = 200, description = "Queued song data wiped", body = super::response::wipe_data_from_song_queue::Response), (status = 200, description = "Queued song data wiped", body = super::response::wipe_data_from_song_queue::Response),
(status = 404, description = "Queued song cannot be found", body = super::response::wipe_data_from_song_queue::Response) (status = 404, description = "Queued song cannot be found", body = super::response::wipe_data_from_song_queue::Response),
(status = 500, description = "Error wiping song data", body = super::response::wipe_data_from_song_queue::Response)
) )
)] )]
pub async fn wipe_data_from_song_queue( pub async fn wipe_data_from_song_queue(
@@ -493,21 +611,37 @@ pub mod endpoint {
axum::Json<super::response::wipe_data_from_song_queue::Response>, axum::Json<super::response::wipe_data_from_song_queue::Response>,
) { ) {
let mut response = super::response::wipe_data_from_song_queue::Response::default(); let mut response = super::response::wipe_data_from_song_queue::Response::default();
let id = payload.song_queue_id; let song_queue_id = payload.song_queue_id;
match repo::song::get_song_queue(&pool, &id).await { match repo::song::get_song_queue(&pool, &song_queue_id).await {
Ok(song_queue) => match repo::song::wipe_data(&pool, &song_queue.id).await { Ok(_song_queue) => {
Ok(wiped_id) => { match repo::data::get_with_song_queue_id(&pool, &song_queue_id).await {
Ok((_id, file_key, _bucket, _region, _)) => {
let lab_config = crate::util::maze::get_config();
let lr = labyrinth::Labyrinth { config: lab_config };
match lr.delete(&file_key).await {
Ok(_) => {
response.message = String::from("Success"); response.message = String::from("Success");
response.data.push(wiped_id); response.data.push(song_queue_id);
(axum::http::StatusCode::OK, axum::Json(response)) (axum::http::StatusCode::OK, axum::Json(response))
} }
Err(err) => {
eprintln!("Error: {err:?}");
(
axum::http::StatusCode::INTERNAL_SERVER_ERROR,
axum::Json(response),
)
}
}
}
Err(err) => { Err(err) => {
response.message = err.to_string(); response.message = err.to_string();
(axum::http::StatusCode::NOT_FOUND, axum::Json(response)) (axum::http::StatusCode::NOT_FOUND, axum::Json(response))
} }
}, }
}
Err(err) => { Err(err) => {
response.message = err.to_string(); response.message = err.to_string();
(axum::http::StatusCode::NOT_FOUND, axum::Json(response)) (axum::http::StatusCode::NOT_FOUND, axum::Json(response))
+93 -24
View File
@@ -36,8 +36,8 @@ pub mod request {
|| !self.song_queue_id.is_nil() || !self.song_queue_id.is_nil()
} }
pub fn to_song(&self) -> icarus_models::song::Song { pub fn to_song(&self) -> simodels::song::Song {
icarus_models::song::Song { simodels::song::Song {
id: uuid::Uuid::nil(), id: uuid::Uuid::nil(),
title: self.title.clone(), title: self.title.clone(),
artist: self.artist.clone(), artist: self.artist.clone(),
@@ -71,7 +71,7 @@ pub mod response {
#[derive(Debug, Default, serde::Deserialize, serde::Serialize, utoipa::ToSchema)] #[derive(Debug, Default, serde::Deserialize, serde::Serialize, utoipa::ToSchema)]
pub struct Response { pub struct Response {
pub message: String, pub message: String,
pub data: Vec<icarus_models::song::Song>, pub data: Vec<simodels::song::Song>,
} }
} }
@@ -79,15 +79,15 @@ pub mod response {
#[derive(Debug, Default, serde::Deserialize, serde::Serialize, utoipa::ToSchema)] #[derive(Debug, Default, serde::Deserialize, serde::Serialize, utoipa::ToSchema)]
pub struct Response { pub struct Response {
pub message: String, pub message: String,
pub data: Vec<icarus_models::song::Song>, pub data: Vec<simodels::song::Song>,
} }
} }
pub mod delete_song { pub mod delete_song {
#[derive(Debug, Default, serde::Deserialize, serde::Serialize, utoipa::ToSchema)] #[derive(Debug, Default, serde::Deserialize, serde::Serialize, utoipa::ToSchema)]
pub struct SongAndCoverArt { pub struct SongAndCoverArt {
pub song: icarus_models::song::Song, pub song: simodels::song::Song,
pub coverart: icarus_models::coverart::CoverArt, pub coverart: simodels::coverart::CoverArt,
} }
#[derive(Debug, Default, serde::Deserialize, serde::Serialize, utoipa::ToSchema)] #[derive(Debug, Default, serde::Deserialize, serde::Serialize, utoipa::ToSchema)]
@@ -134,14 +134,16 @@ pub mod endpoint {
if payload.is_valid() { if payload.is_valid() {
let mut song = payload.to_song(); let mut song = payload.to_song();
song.filename = icarus_models::song::generate_filename( song.filename =
icarus_models::types::MusicType::FlacExtension, simodels::song::generate_filename(simodels::types::MusicType::FlacExtension, true)
true,
)
.unwrap(); .unwrap();
song.directory = icarus_envy::environment::get_root_directory().await.value; song.directory = sienvy::environment::get_root_directory().value;
match repo_queue::song::get_data(&pool, &payload.song_queue_id).await { let lab_config = crate::util::maze::get_config();
let lr = labyrinth::Labyrinth { config: lab_config };
match repo_queue::data::get_with_song_queue_id(&pool, &payload.song_queue_id).await {
Ok((_id, file_key, _bucket, _region, _)) => match lr.download(&file_key).await {
Ok(data) => { Ok(data) => {
song.data = data; song.data = data;
let dir = std::path::Path::new(&song.directory); let dir = std::path::Path::new(&song.directory);
@@ -157,8 +159,14 @@ pub mod endpoint {
} }
} }
match song.save_to_filesystem() { song.file_key = format!("processed/song/{}", song.filename);
Ok(_) => match repo::song::insert(&pool, &song).await { let data_of_song = labyrinth::Data {
raw_data: song.data.clone(),
..Default::default()
};
match lr.upload(&song.file_key, &data_of_song).await {
Ok(_resp) => match repo::song::insert(&pool, &song).await {
Ok((date_created, id)) => { Ok((date_created, id)) => {
song.id = id; song.id = id;
song.date_created = Some(date_created); song.date_created = Some(date_created);
@@ -168,12 +176,13 @@ pub mod endpoint {
(axum::http::StatusCode::OK, axum::Json(response)) (axum::http::StatusCode::OK, axum::Json(response))
} }
Err(err) => { Err(err) => {
response.message = format!("{:?} song {:?}", err.to_string(), song); response.message =
format!("{:?} song {:?}", err.to_string(), song);
(axum::http::StatusCode::BAD_REQUEST, axum::Json(response)) (axum::http::StatusCode::BAD_REQUEST, axum::Json(response))
} }
}, },
Err(err) => { Err(err) => {
response.message = err.to_string(); eprintln!("Error: {err:?}");
( (
axum::http::StatusCode::INTERNAL_SERVER_ERROR, axum::http::StatusCode::INTERNAL_SERVER_ERROR,
axum::Json(response), axum::Json(response),
@@ -181,6 +190,11 @@ pub mod endpoint {
} }
} }
} }
Err(err) => {
eprintln!("Error: {err:?}");
(axum::http::StatusCode::BAD_REQUEST, axum::Json(response))
}
},
Err(err) => { Err(err) => {
response.message = err.to_string(); response.message = err.to_string();
(axum::http::StatusCode::BAD_REQUEST, axum::Json(response)) (axum::http::StatusCode::BAD_REQUEST, axum::Json(response))
@@ -282,17 +296,41 @@ pub mod endpoint {
axum::extract::Path(id): axum::extract::Path<uuid::Uuid>, axum::extract::Path(id): axum::extract::Path<uuid::Uuid>,
) -> impl IntoResponse { ) -> impl IntoResponse {
match repo::song::get_song(&pool, &id).await { match repo::song::get_song(&pool, &id).await {
Ok(song) => { Ok(mut song) => {
let lab_config = crate::util::maze::get_config();
let lr = labyrinth::Labyrinth { config: lab_config };
match lr.download(&song.file_key).await {
Ok(data) => {
song.filename = simodels::song::generate_filename(
simodels::types::MusicType::FlacExtension,
true,
)
.unwrap();
// TODO: At some point, directory will no longer be needed with s3
song.directory = sienvy::environment::get_root_directory().value;
song.data = data;
match song.save_to_filesystem() {
Ok(_) => {
let song_path = song.song_path().unwrap(); let song_path = song.song_path().unwrap();
let path = std::path::Path::new(&song_path); let path = std::path::Path::new(&song_path);
if !path.starts_with(&song.directory) || !path.exists() { if !path.starts_with(&song.directory) || !path.exists() {
return Err((axum::http::StatusCode::NOT_FOUND, "File not found")); return Err((
axum::http::StatusCode::NOT_FOUND,
"File not found",
));
} }
let file = match tokio::fs::File::open(&path).await { let file = match tokio::fs::File::open(&path).await {
Ok(file) => file, Ok(file) => file,
Err(_) => return Err((axum::http::StatusCode::NOT_FOUND, "File not found")), Err(_) => {
return Err((
axum::http::StatusCode::NOT_FOUND,
"File not found",
));
}
}; };
let file_size = match file.metadata().await { let file_size = match file.metadata().await {
@@ -314,8 +352,34 @@ pub mod endpoint {
.header("content-length", file_size.to_string()) .header("content-length", file_size.to_string())
.body(axum::body::Body::from_stream(stream)) .body(axum::body::Body::from_stream(stream))
.unwrap(); .unwrap();
match song.remove_from_filesystem() {
Ok(rep) Ok(_) => Ok(rep),
Err(err) => {
eprintln!("Error: {err:?}");
Err((
axum::http::StatusCode::INTERNAL_SERVER_ERROR,
"Could not read file",
))
}
}
}
Err(err) => {
eprintln!("Error: {err:?}");
Err((
axum::http::StatusCode::INTERNAL_SERVER_ERROR,
"Could not find file",
))
}
}
}
Err(err) => {
eprintln!("Error: {err:?}");
Err((
axum::http::StatusCode::INTERNAL_SERVER_ERROR,
"Could not find file",
))
}
}
} }
Err(_err) => Err(( Err(_err) => Err((
axum::http::StatusCode::INTERNAL_SERVER_ERROR, axum::http::StatusCode::INTERNAL_SERVER_ERROR,
@@ -339,8 +403,11 @@ pub mod endpoint {
axum::Extension(pool): axum::Extension<sqlx::PgPool>, axum::Extension(pool): axum::Extension<sqlx::PgPool>,
axum::extract::Path(id): axum::extract::Path<uuid::Uuid>, axum::extract::Path(id): axum::extract::Path<uuid::Uuid>,
) -> (axum::http::StatusCode, axum::response::Response) { ) -> (axum::http::StatusCode, axum::response::Response) {
let lab_config = crate::util::maze::get_config();
let lr = labyrinth::Labyrinth { config: lab_config };
match repo::song::get_song(&pool, &id).await { match repo::song::get_song(&pool, &id).await {
Ok(song) => match icarus_models::song::io::to_data(&song) { Ok(song) => match lr.download(&song.file_key).await {
Ok(data) => { Ok(data) => {
let bytes = axum::body::Bytes::from(data); let bytes = axum::body::Bytes::from(data);
let mut response = bytes.into_response(); let mut response = bytes.into_response();
@@ -389,6 +456,8 @@ pub mod endpoint {
axum::Json<super::response::delete_song::Response>, axum::Json<super::response::delete_song::Response>,
) { ) {
let mut response = super::response::delete_song::Response::default(); let mut response = super::response::delete_song::Response::default();
let lab_config = crate::util::maze::get_config();
let lr = labyrinth::Labyrinth { config: lab_config };
match repo::song::get_song(&pool, &id).await { match repo::song::get_song(&pool, &id).await {
Ok(song) => { Ok(song) => {
@@ -412,7 +481,7 @@ pub mod endpoint {
match repo::coverart::delete_coverart(&pool, &coverart.id).await match repo::coverart::delete_coverart(&pool, &coverart.id).await
{ {
Ok(deleted_coverart) => { Ok(deleted_coverart) => {
match song.remove_from_filesystem() { match lr.delete(&song.file_key).await {
Ok(_) => match coverart.remove_from_filesystem() { Ok(_) => match coverart.remove_from_filesystem() {
Ok(_) => { Ok(_) => {
response.message = String::from( response.message = String::from(
@@ -430,7 +499,7 @@ pub mod endpoint {
} }
}, },
Err(err) => { Err(err) => {
response.message = err.to_string(); eprintln!("Error: {err:?}");
(axum::http::StatusCode::INTERNAL_SERVER_ERROR, axum::Json(response)) (axum::http::StatusCode::INTERNAL_SERVER_ERROR, axum::Json(response))
} }
} }
+11 -14
View File
@@ -28,20 +28,18 @@ mod cors {
axum::http::Method::POST, axum::http::Method::POST,
axum::http::Method::PUT, axum::http::Method::PUT,
axum::http::Method::DELETE, axum::http::Method::DELETE,
]) // Specify allowed methods:cite[2] ])
.allow_headers([ .allow_headers([
axum::http::header::CONTENT_TYPE, axum::http::header::CONTENT_TYPE,
axum::http::header::AUTHORIZATION, axum::http::header::AUTHORIZATION,
]) // Specify allowed headers:cite[2] ])
.allow_credentials(true) // If you need to send cookies or authentication headers:cite[2] .allow_credentials(true)
.max_age(std::time::Duration::from_secs(3600)); // Cache the preflight response for 1 hour:cite[2] .max_age(std::time::Duration::from_secs(3600)); // Cache the preflight response for 1 hour:cite[2]
// Dynamically set the allowed origin based on the environment match std::env::var(sienvy::keys::APP_ENV).as_deref() {
match std::env::var(icarus_envy::keys::APP_ENV).as_deref() {
Ok("production") => { Ok("production") => {
// In production, allow only your specific, trusted origins let allowed_origins_env = sienvy::environment::get_allowed_origins();
let allowed_origins_env = icarus_envy::environment::get_allowed_origins().await; match sienvy::utility::delimitize(&allowed_origins_env) {
match icarus_envy::utility::delimitize(&allowed_origins_env) {
Ok(alwd) => { Ok(alwd) => {
let allowed_origins: Vec<axum::http::HeaderValue> = alwd let allowed_origins: Vec<axum::http::HeaderValue> = alwd
.into_iter() .into_iter()
@@ -55,15 +53,14 @@ mod cors {
} }
} }
} }
_ => { _ => cors.allow_origin(vec![
// Development (default): Allow localhost origins
cors.allow_origin(vec![
"http://localhost:8000".parse().unwrap(), "http://localhost:8000".parse().unwrap(),
"http://127.0.0.1:8000".parse().unwrap(), "http://127.0.0.1:8000".parse().unwrap(),
"http://localhost:8001".parse().unwrap(),
"http://127.0.0.1:8001".parse().unwrap(),
"http://localhost:4200".parse().unwrap(), "http://localhost:4200".parse().unwrap(),
"http://127.0.0.1:4200".parse().unwrap(), "http://127.0.0.1:4200".parse().unwrap(),
]) ]),
}
} }
} }
} }
@@ -84,7 +81,7 @@ mod cors {
coverart_queue_responses::wipe_data_from_coverart_queue::Response, coverart_responses::get_coverart::Response, coverart_queue_responses::wipe_data_from_coverart_queue::Response, coverart_responses::get_coverart::Response,
metadata_queue_responses::queue_metadata::Response, metadata_queue_responses::fetch_metadata::Response)), metadata_queue_responses::queue_metadata::Response, metadata_queue_responses::fetch_metadata::Response)),
tags( tags(
(name = "Icarus API", description = "Web API to manage music") (name = "soaricarus API", description = "Web API to manage music")
) )
)] )]
struct ApiDoc; struct ApiDoc;
+1 -3
View File
@@ -5,7 +5,7 @@ pub mod connection_settings {
} }
pub async fn create_pool() -> Result<sqlx::PgPool, sqlx::Error> { pub async fn create_pool() -> Result<sqlx::PgPool, sqlx::Error> {
let database_url = icarus_envy::environment::get_db_url().await.value; let database_url = sienvy::environment::get_db_url().value;
println!("Database url: {database_url}"); println!("Database url: {database_url}");
PgPoolOptions::new() PgPoolOptions::new()
@@ -15,8 +15,6 @@ pub async fn create_pool() -> Result<sqlx::PgPool, sqlx::Error> {
} }
pub async fn migrations(pool: &sqlx::PgPool) { pub async fn migrations(pool: &sqlx::PgPool) {
// Run migrations using the sqlx::migrate! macro
// Assumes your migrations are in a ./migrations folder relative to Cargo.toml
sqlx::migrate!("./migrations") sqlx::migrate!("./migrations")
.run(pool) .run(pool)
.await .await
+6
View File
@@ -0,0 +1,6 @@
pub mod auth;
pub mod callers;
pub mod config;
pub mod db;
pub mod repo;
pub mod util;
+441 -360
View File
File diff suppressed because it is too large Load Diff
+7 -7
View File
@@ -2,7 +2,7 @@ use sqlx::Row;
pub async fn create( pub async fn create(
pool: &sqlx::PgPool, pool: &sqlx::PgPool,
coverart: &icarus_models::coverart::CoverArt, coverart: &simodels::coverart::CoverArt,
song_id: &uuid::Uuid, song_id: &uuid::Uuid,
) -> Result<uuid::Uuid, sqlx::Error> { ) -> Result<uuid::Uuid, sqlx::Error> {
let result = sqlx::query( let result = sqlx::query(
@@ -36,7 +36,7 @@ pub async fn create(
pub async fn get_coverart( pub async fn get_coverart(
pool: &sqlx::PgPool, pool: &sqlx::PgPool,
id: &uuid::Uuid, id: &uuid::Uuid,
) -> Result<icarus_models::coverart::CoverArt, sqlx::Error> { ) -> Result<simodels::coverart::CoverArt, sqlx::Error> {
let result = sqlx::query( let result = sqlx::query(
r#" r#"
SELECT id, title, directory, filename, file_type, song_id FROM "coverart" WHERE id = $1; SELECT id, title, directory, filename, file_type, song_id FROM "coverart" WHERE id = $1;
@@ -50,7 +50,7 @@ pub async fn get_coverart(
}); });
match result { match result {
Ok(row) => Ok(icarus_models::coverart::CoverArt { Ok(row) => Ok(simodels::coverart::CoverArt {
id: row id: row
.try_get("id") .try_get("id")
.map_err(|_e| sqlx::Error::RowNotFound) .map_err(|_e| sqlx::Error::RowNotFound)
@@ -84,7 +84,7 @@ pub async fn get_coverart(
pub async fn get_coverart_with_song_id( pub async fn get_coverart_with_song_id(
pool: &sqlx::PgPool, pool: &sqlx::PgPool,
song_id: &uuid::Uuid, song_id: &uuid::Uuid,
) -> Result<icarus_models::coverart::CoverArt, sqlx::Error> { ) -> Result<simodels::coverart::CoverArt, sqlx::Error> {
let result = sqlx::query( let result = sqlx::query(
r#" r#"
SELECT id, title, directory, filename, file_type, song_id FROM "coverart" WHERE song_id = $1; SELECT id, title, directory, filename, file_type, song_id FROM "coverart" WHERE song_id = $1;
@@ -98,7 +98,7 @@ pub async fn get_coverart_with_song_id(
}); });
match result { match result {
Ok(row) => Ok(icarus_models::coverart::CoverArt { Ok(row) => Ok(simodels::coverart::CoverArt {
id: row id: row
.try_get("id") .try_get("id")
.map_err(|_e| sqlx::Error::RowNotFound) .map_err(|_e| sqlx::Error::RowNotFound)
@@ -132,7 +132,7 @@ pub async fn get_coverart_with_song_id(
pub async fn delete_coverart( pub async fn delete_coverart(
pool: &sqlx::PgPool, pool: &sqlx::PgPool,
id: &uuid::Uuid, id: &uuid::Uuid,
) -> Result<icarus_models::coverart::CoverArt, sqlx::Error> { ) -> Result<simodels::coverart::CoverArt, sqlx::Error> {
let result = sqlx::query( let result = sqlx::query(
r#" r#"
DELETE FROM "coverart" DELETE FROM "coverart"
@@ -148,7 +148,7 @@ pub async fn delete_coverart(
}); });
match result { match result {
Ok(row) => Ok(icarus_models::coverart::CoverArt { Ok(row) => Ok(simodels::coverart::CoverArt {
id: row id: row
.try_get("id") .try_get("id")
.map_err(|_e| sqlx::Error::RowNotFound) .map_err(|_e| sqlx::Error::RowNotFound)
+2 -82
View File
@@ -1,16 +1,11 @@
use sqlx::Row; use sqlx::Row;
pub async fn insert( pub async fn insert(pool: &sqlx::PgPool, file_type: &str) -> Result<uuid::Uuid, sqlx::Error> {
pool: &sqlx::PgPool,
data: &Vec<u8>,
file_type: &str,
) -> Result<uuid::Uuid, sqlx::Error> {
let result = sqlx::query( let result = sqlx::query(
r#" r#"
INSERT INTO "coverartQueue" (data, file_type) VALUES($1, $2) RETURNING id; INSERT INTO "coverartQueue" (file_type) VALUES($1) RETURNING id;
"#, "#,
) )
.bind(data)
.bind(file_type) .bind(file_type)
.fetch_one(pool) .fetch_one(pool)
.await .await
@@ -120,78 +115,3 @@ pub async fn get_coverart_queue_with_song_queue_id(
Err(_) => Err(sqlx::Error::RowNotFound), Err(_) => Err(sqlx::Error::RowNotFound),
} }
} }
pub async fn get_coverart_queue_data_with_id(
pool: &sqlx::PgPool,
id: &uuid::Uuid,
) -> Result<Vec<u8>, sqlx::Error> {
let result = sqlx::query(
r#"
SELECT data FROM "coverartQueue" WHERE id = $1;
"#,
)
.bind(id)
.fetch_one(pool)
.await
.map_err(|e| {
eprintln!("Error querying data: {e:?}");
});
match result {
Ok(row) => Ok(row
.try_get("data")
.map_err(|_e| sqlx::Error::RowNotFound)
.unwrap()),
Err(_) => Err(sqlx::Error::RowNotFound),
}
}
pub async fn get_coverart_queue_data_with_song_queue_id(
pool: &sqlx::PgPool,
song_queue_id: &uuid::Uuid,
) -> Result<Vec<u8>, sqlx::Error> {
let result = sqlx::query(
r#"
SELECT data FROM "coverartQueue" WHERE song_queue_id = $1;
"#,
)
.bind(song_queue_id)
.fetch_one(pool)
.await
.map_err(|e| {
eprintln!("Error querying data: {e}");
});
match result {
Ok(row) => Ok(row
.try_get("data")
.map_err(|_e| sqlx::Error::RowNotFound)
.unwrap()),
Err(_) => Err(sqlx::Error::RowNotFound),
}
}
pub async fn wipe_data(
pool: &sqlx::PgPool,
coverart_queue_id: &uuid::Uuid,
) -> Result<uuid::Uuid, sqlx::Error> {
let result = sqlx::query(
r#"
UPDATE "coverartQueue" SET data = NULL WHERE id = $1 RETURNING id;
"#,
)
.bind(coverart_queue_id)
.fetch_one(pool)
.await
.map_err(|e| {
eprintln!("Error updating query: {e}");
});
match result {
Ok(row) => Ok(row
.try_get("id")
.map_err(|_e| sqlx::Error::RowNotFound)
.unwrap()),
Err(_) => Err(sqlx::Error::RowNotFound),
}
}
+204
View File
@@ -0,0 +1,204 @@
use sqlx::Row;
pub async fn insert(
pool: &sqlx::PgPool,
file_key: &str,
bucket: &str,
region: &str,
song_queue_id: &uuid::Uuid,
) -> Result<uuid::Uuid, sqlx::Error> {
match sqlx::query(
r#"
INSERT INTO "songQueueData" (file_key, bucket, region, song_queue_id) VALUES($1, $2, $3, $4) RETURNING id;
"#,
)
.bind(file_key)
.bind(bucket)
.bind(region)
.bind(song_queue_id)
.fetch_one(pool).await {
Ok(row) => {
let id: uuid::Uuid = row.try_get("id")?;
Ok(id)
}
Err(_err) => Err(sqlx::Error::RowNotFound),
}
}
pub async fn update_file_key(
pool: &sqlx::PgPool,
id: &uuid::Uuid,
file_key: &str,
) -> Result<(), sqlx::Error> {
match sqlx::query(
r#"
UPDATE "songQueueData" SET file_key = $1 WHERE id = $2;
"#,
)
.bind(file_key)
.bind(id)
.execute(pool)
.await
{
Ok(_row) => Ok(()),
Err(_) => Err(sqlx::Error::RowNotFound),
}
}
pub async fn get(
pool: &sqlx::PgPool,
id: &uuid::Uuid,
) -> Result<(uuid::Uuid, String, String, String, uuid::Uuid), sqlx::Error> {
match sqlx::query(
r#"
SELECT id, file_key, bucket, region, song_queue_id FROM "songQueueData"
WHERE id = $1
"#,
)
.bind(id)
.fetch_one(pool)
.await
{
Ok(row) => {
let file_key: String = row.try_get("file_key")?;
let bucket: String = row.try_get("bucket")?;
let region: String = row.try_get("region")?;
let song_queue_id: uuid::Uuid = row.try_get("song_queue_id")?;
Ok((*id, file_key, bucket, region, song_queue_id))
}
Err(err) => Err(err),
}
}
pub async fn get_with_song_queue_id(
pool: &sqlx::PgPool,
song_queue_id: &uuid::Uuid,
) -> Result<(uuid::Uuid, String, String, String, uuid::Uuid), sqlx::Error> {
match sqlx::query(
r#"
SELECT id, file_key, bucket, region, song_queue_id FROM "songQueueData"
WHERE song_queue_id = $1
"#,
)
.bind(song_queue_id)
.fetch_one(pool)
.await
{
Ok(row) => {
let id: uuid::Uuid = row.try_get("id")?;
let file_key: String = row.try_get("file_key")?;
let bucket: String = row.try_get("bucket")?;
let region: String = row.try_get("region")?;
Ok((id, file_key, bucket, region, *song_queue_id))
}
Err(err) => Err(err),
}
}
pub mod queue {
pub mod coverart {
use sqlx::Row;
pub async fn insert(
pool: &sqlx::PgPool,
file_key: &str,
bucket: &str,
region: &str,
coverart_queue_id: &uuid::Uuid,
) -> Result<uuid::Uuid, sqlx::Error> {
match sqlx::query(
r#"
INSERT INTO "coverartQueueData" (file_key, bucket, region, coverart_queue_id)
VALUES($1, $2, $3, $4) RETURNING id;
"#,
)
.bind(file_key)
.bind(bucket)
.bind(region)
.bind(coverart_queue_id)
.fetch_one(pool)
.await
{
Ok(row) => {
let id: uuid::Uuid = row.try_get("id")?;
Ok(id)
}
Err(_err) => Err(sqlx::Error::RowNotFound),
}
}
pub async fn update_file_key(
pool: &sqlx::PgPool,
id: &uuid::Uuid,
file_key: &str,
) -> Result<(), sqlx::Error> {
match sqlx::query(
r#"
UPDATE "coverartQueueData" SET file_key = $1 WHERE id = $2;
"#,
)
.bind(file_key)
.bind(id)
.execute(pool)
.await
{
Ok(_row) => Ok(()),
Err(_) => Err(sqlx::Error::RowNotFound),
}
}
pub async fn get(
pool: &sqlx::PgPool,
id: &uuid::Uuid,
) -> Result<(uuid::Uuid, String, String, String, uuid::Uuid), sqlx::Error> {
match sqlx::query(
r#"
SELECT id, file_key, bucket, region, coverart_queue_id FROM "coverartQueueData"
WHERE id = $1
"#,
)
.bind(id)
.fetch_one(pool)
.await
{
Ok(row) => {
let file_key: String = row.try_get("file_key")?;
let bucket: String = row.try_get("bucket")?;
let region: String = row.try_get("region")?;
let coverart_queue_id: uuid::Uuid = row.try_get("coverart_queue_id")?;
Ok((*id, file_key, bucket, region, coverart_queue_id))
}
Err(err) => Err(err),
}
}
pub async fn get_with_coverart_queue_id(
pool: &sqlx::PgPool,
coverart_queue_id: &uuid::Uuid,
) -> Result<(uuid::Uuid, String, String, String, uuid::Uuid), sqlx::Error> {
match sqlx::query(
r#"
SELECT id, file_key, bucket, region, coverart_queue_id FROM "coverartQueueData"
WHERE coverart_queue_id = $1
"#,
)
.bind(coverart_queue_id)
.fetch_one(pool)
.await
{
Ok(row) => {
let id: uuid::Uuid = row.try_get("id")?;
let file_key: String = row.try_get("file_key")?;
let bucket: String = row.try_get("bucket")?;
let region: String = row.try_get("region")?;
Ok((id, file_key, bucket, region, *coverart_queue_id))
}
Err(err) => Err(err),
}
}
}
}
+1
View File
@@ -1,3 +1,4 @@
pub mod coverart; pub mod coverart;
pub mod data;
pub mod metadata; pub mod metadata;
pub mod song; pub mod song;
+1 -51
View File
@@ -24,16 +24,14 @@ pub mod dbtype {
pub async fn insert( pub async fn insert(
pool: &sqlx::PgPool, pool: &sqlx::PgPool,
data: &Vec<u8>,
filename: &String, filename: &String,
status: &String, status: &String,
) -> Result<uuid::Uuid, sqlx::Error> { ) -> Result<uuid::Uuid, sqlx::Error> {
let result = sqlx::query( let result = sqlx::query(
r#" r#"
INSERT INTO "songQueue" (data, filename, status) VALUES($1, $2, $3) RETURNING id; INSERT INTO "songQueue" (filename, status) VALUES($1, $2) RETURNING id;
"#, "#,
) )
.bind(data)
.bind(filename) .bind(filename)
.bind(status) .bind(status)
.fetch_one(pool) .fetch_one(pool)
@@ -253,51 +251,3 @@ pub async fn get_song_queue(
Err(_err) => Err(sqlx::Error::RowNotFound), Err(_err) => Err(sqlx::Error::RowNotFound),
} }
} }
pub async fn wipe_data(pool: &sqlx::PgPool, id: &uuid::Uuid) -> Result<uuid::Uuid, sqlx::Error> {
let result = sqlx::query(
r#"
UPDATE "songQueue" SET data = NULL WHERE id = $1 RETURNING id;
"#,
)
.bind(id)
.fetch_one(pool)
.await
.map_err(|e| {
eprintln!("Error updating record: {e}");
});
match result {
Ok(row) => Ok(row
.try_get("id")
.map_err(|_e| sqlx::Error::RowNotFound)
.unwrap()),
Err(_) => Err(sqlx::Error::RowNotFound),
}
}
pub async fn get_data(pool: &sqlx::PgPool, id: &uuid::Uuid) -> Result<Vec<u8>, sqlx::Error> {
let result = sqlx::query(
r#"
SELECT data FROM "songQueue"
WHERE id = $1;
"#,
)
.bind(id)
.fetch_one(pool)
.await
.map_err(|e| {
eprintln!("Error inserting: {e}");
});
match result {
Ok(row) => {
let data = row
.try_get("data")
.map_err(|_e| sqlx::Error::RowNotFound)
.unwrap();
Ok(data)
}
Err(_err) => Err(sqlx::Error::RowNotFound),
}
}
+25 -14
View File
@@ -2,12 +2,12 @@ use sqlx::Row;
pub async fn insert( pub async fn insert(
pool: &sqlx::PgPool, pool: &sqlx::PgPool,
song: &icarus_models::song::Song, song: &simodels::song::Song,
) -> Result<(time::OffsetDateTime, uuid::Uuid), sqlx::Error> { ) -> Result<(time::OffsetDateTime, uuid::Uuid), sqlx::Error> {
let result = sqlx::query( let result = sqlx::query(
r#" r#"
INSERT INTO "song" (title, artist, album_artist, album, genre, year, track, disc, track_count, disc_count, duration, audio_type, filename, directory, user_id) INSERT INTO "song" (title, artist, album_artist, album, genre, year, track, disc, track_count, disc_count, duration, audio_type, filename, directory, file_key, user_id)
VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15) RETURNING date_created, id; VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16) RETURNING date_created, id;
"# "#
) )
.bind(&song.title) .bind(&song.title)
@@ -24,6 +24,7 @@ pub async fn insert(
.bind(&song.audio_type) .bind(&song.audio_type)
.bind(&song.filename) .bind(&song.filename)
.bind(&song.directory) .bind(&song.directory)
.bind(&song.file_key)
.bind(song.user_id) .bind(song.user_id)
.fetch_one(pool) .fetch_one(pool)
.await .await
@@ -52,7 +53,7 @@ pub async fn insert(
pub async fn get_song( pub async fn get_song(
pool: &sqlx::PgPool, pool: &sqlx::PgPool,
id: &uuid::Uuid, id: &uuid::Uuid,
) -> Result<icarus_models::song::Song, sqlx::Error> { ) -> Result<simodels::song::Song, sqlx::Error> {
let result = sqlx::query( let result = sqlx::query(
r#" r#"
SELECT * FROM "song" WHERE id = $1 SELECT * FROM "song" WHERE id = $1
@@ -72,7 +73,7 @@ pub async fn get_song(
.map_err(|_e| sqlx::Error::RowNotFound) .map_err(|_e| sqlx::Error::RowNotFound)
.unwrap(); .unwrap();
Ok(icarus_models::song::Song { Ok(simodels::song::Song {
id: row id: row
.try_get("id") .try_get("id")
.map_err(|_e| sqlx::Error::RowNotFound) .map_err(|_e| sqlx::Error::RowNotFound)
@@ -133,6 +134,10 @@ pub async fn get_song(
.try_get("directory") .try_get("directory")
.map_err(|_e| sqlx::Error::RowNotFound) .map_err(|_e| sqlx::Error::RowNotFound)
.unwrap(), .unwrap(),
file_key: row
.try_get("file_key")
.map_err(|_e| sqlx::Error::RowNotFound)
.unwrap(),
date_created: Some(date_created_time), date_created: Some(date_created_time),
user_id: row user_id: row
.try_get("user_id") .try_get("user_id")
@@ -145,9 +150,7 @@ pub async fn get_song(
} }
} }
pub async fn get_all_songs( pub async fn get_all_songs(pool: &sqlx::PgPool) -> Result<Vec<simodels::song::Song>, sqlx::Error> {
pool: &sqlx::PgPool,
) -> Result<Vec<icarus_models::song::Song>, sqlx::Error> {
let result = sqlx::query( let result = sqlx::query(
r#" r#"
SELECT * FROM "song"; SELECT * FROM "song";
@@ -161,7 +164,7 @@ pub async fn get_all_songs(
match result { match result {
Ok(rows) => { Ok(rows) => {
let mut songs: Vec<icarus_models::song::Song> = Vec::new(); let mut songs: Vec<simodels::song::Song> = Vec::new();
for row in rows { for row in rows {
let date_created_time: time::OffsetDateTime = row let date_created_time: time::OffsetDateTime = row
@@ -169,7 +172,7 @@ pub async fn get_all_songs(
.map_err(|_e| sqlx::Error::RowNotFound) .map_err(|_e| sqlx::Error::RowNotFound)
.unwrap(); .unwrap();
let song = icarus_models::song::Song { let song = simodels::song::Song {
id: row id: row
.try_get("id") .try_get("id")
.map_err(|_e| sqlx::Error::RowNotFound) .map_err(|_e| sqlx::Error::RowNotFound)
@@ -230,6 +233,10 @@ pub async fn get_all_songs(
.try_get("directory") .try_get("directory")
.map_err(|_e| sqlx::Error::RowNotFound) .map_err(|_e| sqlx::Error::RowNotFound)
.unwrap(), .unwrap(),
file_key: row
.try_get("file_key")
.map_err(|_e| sqlx::Error::RowNotFound)
.unwrap(),
date_created: Some(date_created_time), date_created: Some(date_created_time),
user_id: row user_id: row
.try_get("user_id") .try_get("user_id")
@@ -250,13 +257,13 @@ pub async fn get_all_songs(
pub async fn delete_song( pub async fn delete_song(
pool: &sqlx::PgPool, pool: &sqlx::PgPool,
id: &uuid::Uuid, id: &uuid::Uuid,
) -> Result<icarus_models::song::Song, sqlx::Error> { ) -> Result<simodels::song::Song, sqlx::Error> {
let result = sqlx::query( let result = sqlx::query(
// icarus_models::song::Song, // simodels::song::Song,
r#" r#"
DELETE FROM "song" DELETE FROM "song"
WHERE id = $1 WHERE id = $1
RETURNING id, title, artist, album, album_artist, genre, year, disc, track, track_count, disc_count, duration, audio_type, date_created, filename, directory, user_id RETURNING id, title, artist, album, album_artist, genre, year, disc, track, track_count, disc_count, duration, audio_type, date_created, filename, directory, file_key, user_id
"#, "#,
) )
.bind(id) .bind(id)
@@ -273,7 +280,7 @@ pub async fn delete_song(
.map_err(|_e| sqlx::Error::RowNotFound) .map_err(|_e| sqlx::Error::RowNotFound)
.unwrap(); .unwrap();
Ok(icarus_models::song::Song { Ok(simodels::song::Song {
id: row id: row
.try_get("id") .try_get("id")
.map_err(|_e| sqlx::Error::RowNotFound) .map_err(|_e| sqlx::Error::RowNotFound)
@@ -334,6 +341,10 @@ pub async fn delete_song(
.try_get("directory") .try_get("directory")
.map_err(|_e| sqlx::Error::RowNotFound) .map_err(|_e| sqlx::Error::RowNotFound)
.unwrap(), .unwrap(),
file_key: row
.try_get("file_key")
.map_err(|_e| sqlx::Error::RowNotFound)
.unwrap(),
date_created: Some(date_created_time), date_created: Some(date_created_time),
user_id: row user_id: row
.try_get("user_id") .try_get("user_id")
+17
View File
@@ -0,0 +1,17 @@
pub mod maze {
pub fn get_config() -> labyrinth::config::Config {
let s3_endpoint_url = sienvy::environment::get_env("S3_ENDPOINT_URL");
let bucket_name = sienvy::environment::get_env("S3_BUCKET_NAME");
let region = sienvy::environment::get_env("GARAGE_S3_REGION");
let access_key_id = sienvy::environment::get_env("GARAGE_DEFAULT_ACCESS_KEY");
let secret_key = sienvy::environment::get_env("GARAGE_DEFAULT_SECRET_KEY");
labyrinth::config::Config {
url: s3_endpoint_url.value,
bucket: bucket_name.value,
region: region.value,
access_key_id: access_key_id.value,
secret_key: secret_key.value,
}
}
}
+17 -2
View File
@@ -6,10 +6,17 @@ CREATE TABLE IF NOT EXISTS "songQueue" (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
filename TEXT NOT NULL, filename TEXT NOT NULL,
status TEXT CHECK (status IN ('pending', 'ready', 'processing', 'done')), status TEXT CHECK (status IN ('pending', 'ready', 'processing', 'done')),
data BYTEA NULL,
user_id UUID NULL user_id UUID NULL
); );
CREATE TABLE IF NOT EXISTS "songQueueData" (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
file_key TEXT NOT NULL,
bucket TEXT NOT NULL,
region TEXT NOT NULL,
song_queue_id UUID NOT NULL
);
-- Table to store queued metadata -- Table to store queued metadata
CREATE TABLE IF NOT EXISTS "metadataQueue" ( CREATE TABLE IF NOT EXISTS "metadataQueue" (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
@@ -21,11 +28,18 @@ CREATE TABLE IF NOT EXISTS "metadataQueue" (
-- Table to store queued coverart -- Table to store queued coverart
CREATE TABLE IF NOT EXISTS "coverartQueue" ( CREATE TABLE IF NOT EXISTS "coverartQueue" (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
data BYTEA NULL,
file_type TEXT NOT NULL, file_type TEXT NOT NULL,
song_queue_id UUID NULL song_queue_id UUID NULL
); );
CREATE TABLE IF NOT EXISTS "coverartQueueData" (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
file_key TEXT NOT NULL,
bucket TEXT NOT NULL,
region TEXT NOT NULL,
coverart_queue_id UUID NOT NULL
);
-- Create an index for better query performance -- Create an index for better query performance
CREATE INDEX metadata_queue_data_metadata ON "metadataQueue" USING gin (metadata); CREATE INDEX metadata_queue_data_metadata ON "metadataQueue" USING gin (metadata);
@@ -47,6 +61,7 @@ CREATE TABLE IF NOT EXISTS "song" (
date_created timestamptz DEFAULT now(), date_created timestamptz DEFAULT now(),
filename TEXT NOT NULL, filename TEXT NOT NULL,
directory TEXT NOT NULL, directory TEXT NOT NULL,
file_key TEXT NOT NULL,
user_id UUID NULL user_id UUID NULL
); );
@@ -1,6 +1,11 @@
-- Add migration script here -- 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 "song" (id, title, artist, album_artist, album, genre, year, track, disc, track_count, disc_count, duration, audio_type, date_created, filename, directory, file_key, user_id)
INSERT INTO "coverart" (id, title, directory, filename, file_type, song_id) VALUES('996122cd-5ae9-4013-9934-60768d3006ed', 'I', 'tests/I', 'Coverart-1.jpg', 'jpeg', '44cf7940-34ff-489f-9124-d0ec90a55af9'); 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', 'processed/song/track01.flac', '47491f9b-725a-4ba4-b9a5-711e1be46670');
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('94cf7940-34ff-489f-9124-d0ec90a55af4', 'It''s Too Late', 'Kuoth', 'Kuoth', 'I', 'Alternative Hip-Hop', 2020, 2, 1, 9, 1, 116, 'flac', '2020-01-01 13:01:00-05', 'track02.flac', 'tests/I', '47491f9b-725a-4ba4-b9a5-711e1be46670'); INSERT INTO "coverart" (id, title, directory, filename, file_type, song_id)
INSERT INTO "coverart" (id, title, directory, filename, file_type, song_id) VALUES('d96122cd-5ae9-4013-9934-60768d3006e9', 'I', 'tests/I', 'Coverart-2.jpg', 'jpeg', '94cf7940-34ff-489f-9124-d0ec90a55af4'); VALUES('996122cd-5ae9-4013-9934-60768d3006ed', 'I', 'tests/I', 'Coverart-1.jpg', 'jpeg', '44cf7940-34ff-489f-9124-d0ec90a55af9');
INSERT INTO "song" (id, title, artist, album_artist, album, genre, year, track, disc, track_count, disc_count, duration, audio_type, date_created, filename, directory, file_key, user_id)
VALUES('94cf7940-34ff-489f-9124-d0ec90a55af4', 'It''s Too Late', 'Kuoth', 'Kuoth', 'I', 'Alternative Hip-Hop', 2020, 2, 1, 9, 1, 116, 'flac', '2020-01-01 13:01:00-05', 'track02.flac', 'tests/I', 'processed/song/track02.flac', '47491f9b-725a-4ba4-b9a5-711e1be46670');
INSERT INTO "coverart" (id, title, directory, filename, file_type, song_id)
VALUES('d96122cd-5ae9-4013-9934-60768d3006e9', 'I', 'tests/I', 'Coverart-2.jpg', 'jpeg', '94cf7940-34ff-489f-9124-d0ec90a55af4');