Compare commits
137 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 21a97c150c | |||
| 679a4ad322 | |||
| 9aefeace23 | |||
| a0c6ae65a3 | |||
| c7230d3b32 | |||
| 703fc49f32 | |||
| e98e055de0 | |||
| a8603361c0 | |||
| dbda9a3897 | |||
| a987e438c4 | |||
| a150d34c6c | |||
| 34e292bea9 | |||
| c9bcea5f98 | |||
| 6c9e4809a9 | |||
| 4c5bd1ccde | |||
| 5a2639cb59 | |||
| fd9df41f51 | |||
| 7fdea3491f | |||
| 3cde57dca8 | |||
| 8c93429647 | |||
| 5721c387ea | |||
| a624702813 | |||
| 9c67bfd8a8 | |||
| fbec3305b1 | |||
| fa792be9cc | |||
| 6f2e8d6e17 | |||
| eb1f19a96e | |||
| bb766acf4e | |||
| 95688cccfa | |||
| e2485cec44 | |||
| df1562f5e7 | |||
| e00e9cf185 | |||
| f0d2b9de71 | |||
| f0a0bee22b | |||
| 0b73c055aa | |||
| d4c025511d | |||
| b2a4faec76 | |||
| 7d27aed314 | |||
| 961f99b744 | |||
| b1f5c5f604 | |||
| ddb0bf27f8 | |||
| d491767de4 | |||
| 8755276b48 | |||
| fd41d25c4c | |||
| 5e3ca1861d | |||
| db7350b550 | |||
| f823a99984 | |||
| 2823452198 | |||
| 0ba2d4e8b9 | |||
| 4fdd660988 | |||
| b6fe9a070c | |||
| 6e4eeafe13 | |||
| 6f320acb7c | |||
| 9dd7ffa2b6 | |||
| fbcbd41809 | |||
| 01d315e25e | |||
| caf9f08d8a | |||
| 7227eb9420 | |||
| 12c4f56a22 | |||
| 6d650b7962 | |||
| 73bb417bde | |||
| b0277077f4 | |||
| 3edf6424d0 | |||
| dcb51448aa | |||
| 9d90247bf8 | |||
| 86e54e086a | |||
| fd3ce9f96a | |||
| a89580ac70 | |||
| 8900afdfd2 | |||
| 4b3fa78336 | |||
| 71656aa940 | |||
| 562bc87822 | |||
| ebb15b7cdb | |||
| 270b7d059c | |||
| 23c50de468 | |||
| ececcb5ba3 | |||
| 8658dd34ab | |||
| c17c9cd329 | |||
| 37cfda84b5 | |||
| 3ee17a77a8 | |||
| 522be59973 | |||
| 712a0a9a4a | |||
| 74569d85b6 | |||
| b660f8361f | |||
| d460b5d5d6 | |||
| 4a1c1f1f78 | |||
| 66b5944f92 | |||
| 80628292e2 | |||
| 57f4ec1261 | |||
| c191d7ee7a | |||
| 73bfff3940 | |||
| 38c056cc99 | |||
| 1643f78720 | |||
| 06bb52dfa5 | |||
| ab89d1602f | |||
| 640971dea8 | |||
| 664b0d520b | |||
| 11fe1f29e1 | |||
| 05b5de0939 | |||
| d5944c470b | |||
| 00ef8d0242 | |||
| 77c6ee00ea | |||
| 1bfd0a4931 | |||
| 6c3ce69873 | |||
| a423e6e220 | |||
| f8ec65fd29 | |||
| 665407aac5 | |||
| e5bea187f4 | |||
| fd0f487615 | |||
| d48716a54f | |||
| 2cc13ac9bb | |||
| 467a9d7e0f | |||
| f0551a4801 | |||
| 2cd8c83e28 | |||
| 9797897b76 | |||
| 27cc80d114 | |||
| 27ee3d6ba5 | |||
| 1b6e70ce12 | |||
| 5669cdbdd0 | |||
| 3a6e0f64bc | |||
| aa0a1ab5fe | |||
| b529731c95 | |||
| 64e6f33d7c | |||
| 023298c6f8 | |||
| f8d9c8e4a7 | |||
| 471fa71789 | |||
| edaea68296 | |||
| 8600d9b6bc | |||
| 922e527819 | |||
| 46f0da4a5f | |||
| 16839b9bf8 | |||
| 552d5dcd25 | |||
| cadcf33ed6 | |||
| 4f46f2ed94 | |||
| 560773bfff | |||
| 43623d07c3 | |||
| b93d436c29 |
@@ -0,0 +1,21 @@
|
||||
# Ignore build artifacts
|
||||
target/
|
||||
pkg/
|
||||
|
||||
# Ignore git directory
|
||||
.git/
|
||||
|
||||
.github/
|
||||
|
||||
# Ignore environment files (configure via docker-compose instead)
|
||||
.env*
|
||||
|
||||
# Ignore IDE/editor specific files
|
||||
.idea/
|
||||
.vscode/
|
||||
|
||||
# Ignore OS specific files
|
||||
*.DS_Store
|
||||
|
||||
# Add any other files/directories you don't need in the image
|
||||
# e.g., logs/, tmp/
|
||||
@@ -0,0 +1,7 @@
|
||||
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=main_db
|
||||
DATABASE_URL=postgres://${POSTGRES_MAIN_USER}:${POSTGRES_MAIN_PASSWORD}@${POSTGRES_MAIN_HOST}:5432/${POSTGRES_MAIN_DB}
|
||||
@@ -0,0 +1,7 @@
|
||||
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}
|
||||
@@ -0,0 +1,50 @@
|
||||
name: Release Tagging
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "v0.2" ]
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0 # Important for git describe --tags
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: 1.88.0
|
||||
components: cargo
|
||||
|
||||
- name: Extract Version from Cargo.toml
|
||||
id: version
|
||||
run: |
|
||||
VERSION=$(grep '^version = "' Cargo.toml | sed -E 's/version = "([^"]+)"/\1/')
|
||||
PROJECT_COMMIT_HASH=$(git rev-parse HEAD | cut -c 1-10)
|
||||
BRANCH_REF="${GITHUB_REF}"
|
||||
BRANCH_NAME=$(echo "$BRANCH_REF" | cut -d '/' -f 3)
|
||||
PROJECT_TAG_RELEASE="v$VERSION-$BRANCH_NAME-$PROJECT_COMMIT_HASH"
|
||||
echo "::set-output name=project_tag_release::$PROJECT_TAG_RELEASE-961"
|
||||
echo "Version: $VERSION"
|
||||
echo "Hash: $PROJECT_COMMIT_HASH"
|
||||
echo "Branch: $BRANCH_NAME"
|
||||
echo "Tag Release: $PROJECT_TAG_RELEASE"
|
||||
|
||||
- name: Print version
|
||||
id: print_version
|
||||
run: |
|
||||
echo "Printing version"
|
||||
echo "Version: ${{ steps.version.outputs.project_tag_release }}"
|
||||
|
||||
- name: Create GitHub Release
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ steps.version.outputs.project_tag_release }}
|
||||
release_name: Release ${{ steps.version.outputs.project_tag_release }}
|
||||
body: |
|
||||
Release of version ${{ steps.version.outputs.project_tag_release }}
|
||||
@@ -0,0 +1,79 @@
|
||||
name: Rust CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "v0.2" ]
|
||||
pull_request:
|
||||
branches: [ "v0.2" ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-24.04
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:17.5
|
||||
env:
|
||||
POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }}
|
||||
POSTGRES_USER: ${{ secrets.POSTGRES_USER }}
|
||||
POSTGRES_DB: ${{ secrets.POSTGRES_DB }}
|
||||
ports:
|
||||
- 5432:5432
|
||||
options: >-
|
||||
--health-cmd pg_isready
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust 1.88.0
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: 1.88.0
|
||||
components: clippy, rustfmt
|
||||
override: true
|
||||
|
||||
- name: Cache dependencies
|
||||
uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: Install libpq
|
||||
run: sudo apt-get install -y libpq-dev
|
||||
|
||||
- name: Setup test database
|
||||
env:
|
||||
DATABASE_URL: "postgres://${{ secrets.POSTGRES_USER }}:${{ secrets.POSTGRES_PASSWORD }}@localhost:5432/${{ secrets.POSTGRES_DB }}"
|
||||
run: |
|
||||
# Wait for PostgreSQL to be ready
|
||||
for i in {1..10}; do
|
||||
pg_isready -U ${{ secrets.POSTGRES_USER }} -d ${{ secrets.POSTGRES_DB }} && break
|
||||
sleep 2
|
||||
done
|
||||
|
||||
# Run database migrations (if you use sqlx migrations)
|
||||
cargo install sqlx-cli --no-default-features --features native-tls,postgres
|
||||
sqlx migrate run --database-url $DATABASE_URL
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
mkdir -p ~/.ssh
|
||||
echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/gitea_deploy_key
|
||||
chmod 600 ~/.ssh/gitea_deploy_key
|
||||
ssh-keyscan ${{ vars.MYHOST }} >> ~/.ssh/known_hosts
|
||||
eval $(ssh-agent -s)
|
||||
ssh-add -v ~/.ssh/gitea_deploy_key
|
||||
cargo build --verbose --release
|
||||
|
||||
- name: Run tests
|
||||
env:
|
||||
ROOT_DIRECTORY: "/tmp"
|
||||
DATABASE_URL: "postgres://${{ secrets.POSTGRES_USER }}:${{ secrets.POSTGRES_PASSWORD }}@localhost:5432/${{ secrets.POSTGRES_DB }}"
|
||||
run: |
|
||||
cat .env.sample | head -1 > .env
|
||||
cargo test --verbose --
|
||||
|
||||
- name: Run clippy
|
||||
run: cargo clippy -- -D warnings
|
||||
|
||||
- name: Run rustfmt
|
||||
run: cargo fmt --all -- --check
|
||||
@@ -1,8 +1,9 @@
|
||||
################################################################################
|
||||
# This .gitignore file was automatically created by Microsoft(R) Visual Studio.
|
||||
################################################################################
|
||||
# Added by cargo
|
||||
|
||||
/.vs/Icarus
|
||||
/bin/Debug/netcoreapp2.2
|
||||
/Migrations
|
||||
/obj
|
||||
/target
|
||||
.env
|
||||
.env.docker
|
||||
.env.local
|
||||
|
||||
.DS_STORE
|
||||
Storage/
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
[submodule "Libs/cpr"]
|
||||
path = 3rdparty/cpr
|
||||
url = https://github.com/whoshuu/cpr.git
|
||||
[submodule "3rdparty/taglib"]
|
||||
path = 3rdparty/taglib
|
||||
url = https://github.com/taglib/taglib
|
||||
[submodule "3rdparty/oatpp"]
|
||||
path = 3rdparty/oatpp
|
||||
url = https://github.com/oatpp/oatpp
|
||||
[submodule "build/3rdparty/jwt-cpp"]
|
||||
path = build/3rdparty/jwt-cpp
|
||||
url = https://github.com/Thalhammer/jwt-cpp
|
||||
[submodule "3rdparty/jwt-cpp"]
|
||||
path = 3rdparty/jwt-cpp
|
||||
url = https://github.com/Thalhammer/jwt-cpp
|
||||
[submodule "3rdparty/ormpp"]
|
||||
path = 3rdparty/ormpp
|
||||
url = https://github.com/qicosmos/ormpp
|
||||
[submodule "3rdparty/libbcrypt"]
|
||||
path = 3rdparty/libbcrypt
|
||||
url = https://github.com/rg3/libbcrypt
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"dev": {
|
||||
"migrations": "./migrations"
|
||||
},
|
||||
"test": {
|
||||
"migrations": "./test_migrations"
|
||||
}
|
||||
}
|
||||
@@ -1,149 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
|
||||
project(icarus)
|
||||
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
|
||||
set(SOURCES
|
||||
src/callback/StreamCallback.cpp
|
||||
src/database/AlbumRepository.cpp
|
||||
src/database/ArtistRepository.cpp
|
||||
src/database/BaseRepository.cpp
|
||||
src/database/CoverArtRepository.cpp
|
||||
src/database/GenreRepository.cpp
|
||||
src/database/SongRepository.cpp
|
||||
src/database/UserRepository.cpp
|
||||
src/database/YearRepository.cpp
|
||||
src/dto/conversion/DtoConversions.cpp
|
||||
src/Main.cpp
|
||||
src/manager/AlbumManager.cpp
|
||||
src/manager/ArtistManager.cpp
|
||||
src/manager/CoverArtManager.cpp
|
||||
src/manager/DirectoryManager.cpp
|
||||
src/manager/GenreManager.cpp
|
||||
src/manager/SongManager.cpp
|
||||
src/manager/TokenManager.cpp
|
||||
src/manager/UserManager.cpp
|
||||
src/manager/YearManager.cpp
|
||||
src/utility/ImageFile.cpp
|
||||
src/utility/MetadataRetriever.cpp
|
||||
src/utility/PasswordEncryption.cpp
|
||||
src/verify/Initialization.cpp
|
||||
)
|
||||
set(HEADERS
|
||||
include/callback/StreamCallback.h
|
||||
include/component/AppComponent.hpp
|
||||
include/controller/AlbumController.hpp
|
||||
include/controller/ArtistController.hpp
|
||||
include/controller/CoverArtController.hpp
|
||||
include/controller/GenreController.hpp
|
||||
include/controller/LoginController.hpp
|
||||
include/controller/RegisterController.hpp
|
||||
include/controller/SongController.hpp
|
||||
include/controller/YearController.hpp
|
||||
include/database/AlbumRepository.h
|
||||
include/database/ArtistRepository.h
|
||||
include/database/BaseRepository.h
|
||||
include/database/CoverArtRepository.h
|
||||
include/database/GenreRepository.h
|
||||
include/database/SongRepository.h
|
||||
include/database/UserRepository.h
|
||||
include/database/YearRepository.h
|
||||
include/dto/AlbumDto.hpp
|
||||
include/dto/ArtistDto.hpp
|
||||
include/dto/CoverArtDto.hpp
|
||||
include/dto/GenreDto.hpp
|
||||
include/dto/LoginResultDto.hpp
|
||||
include/dto/SongDto.hpp
|
||||
include/dto/YearDto.hpp
|
||||
include/dto/conversion/DtoConversions.h
|
||||
include/manager/AlbumManager.h
|
||||
include/manager/ArtistManager.h
|
||||
include/manager/CoverArtManager.h
|
||||
include/manager/DirectoryManager.h
|
||||
include/manager/GenreManager.h
|
||||
include/manager/SongManager.h
|
||||
include/manager/TokenManager.h
|
||||
include/manager/UserManager.h
|
||||
include/manager/YearManager.h
|
||||
include/model/Models.h
|
||||
include/utility/ImageFile.h
|
||||
include/utility/MetadataRetriever.h
|
||||
include/utility/PasswordEncryption.h
|
||||
include/type/AlbumFilter.h
|
||||
include/type/ArtistFilter.h
|
||||
include/type/CoverFilter.h
|
||||
include/type/GenreFilter.h
|
||||
include/type/PathType.h
|
||||
include/type/SaltFilter.h
|
||||
include/type/Scopes.h
|
||||
include/type/SongChanged.h
|
||||
include/type/SongFilter.h
|
||||
include/type/UserFilter.h
|
||||
include/type/YearFilter.h
|
||||
include/verify/Initialization.h
|
||||
)
|
||||
|
||||
set (TAGLIB
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/taglib/3rdparty
|
||||
)
|
||||
|
||||
set (BCRYPTLIB
|
||||
${CMAKE_SOURCE_DIR}/3rdparty/libbcrypt
|
||||
)
|
||||
|
||||
set(TAGLIB_HEADERS
|
||||
"${CMAKE_SOURCE_DIR}/build/3rdparty/taglib"
|
||||
"${CMAKE_SOURCE_DIR}/3rdparty/taglib"
|
||||
"${CMAKE_SOURCE_DIR}/3rdparty/taglib/taglib"
|
||||
"${CMAKE_SOURCE_DIR}/3rdparty/taglib/taglib/ape"
|
||||
"${CMAKE_SOURCE_DIR}/3rdparty/taglib/taglib/asf"
|
||||
"${CMAKE_SOURCE_DIR}/3rdparty/taglib/taglib/dsdiff"
|
||||
"${CMAKE_SOURCE_DIR}/3rdparty/taglib/taglib/dsf"
|
||||
"${CMAKE_SOURCE_DIR}/3rdparty/taglib/taglib/flac"
|
||||
"${CMAKE_SOURCE_DIR}/3rdparty/taglib/taglib/it"
|
||||
"${CMAKE_SOURCE_DIR}/3rdparty/taglib/taglib/mod"
|
||||
"${CMAKE_SOURCE_DIR}/3rdparty/taglib/taglib/mp4"
|
||||
"${CMAKE_SOURCE_DIR}/3rdparty/taglib/taglib/mpc"
|
||||
"${CMAKE_SOURCE_DIR}/3rdparty/taglib/taglib/mpeg"
|
||||
"${CMAKE_SOURCE_DIR}/3rdparty/taglib/taglib/mpeg/id3v2"
|
||||
"${CMAKE_SOURCE_DIR}/3rdparty/taglib/taglib/mpeg/id3v2/frames"
|
||||
"${CMAKE_SOURCE_DIR}/3rdparty/taglib/taglib/ogg"
|
||||
"${CMAKE_SOURCE_DIR}/3rdparty/taglib/taglib/riff"
|
||||
"${CMAKE_SOURCE_DIR}/3rdparty/taglib/taglib/s3m"
|
||||
"${CMAKE_SOURCE_DIR}/3rdparty/taglib/taglib/toolkit"
|
||||
"${CMAKE_SOURCE_DIR}/3rdparty/taglib/taglib/trueaudio"
|
||||
"${CMAKE_SOURCE_DIR}/3rdparty/taglib/taglib/wavpack"
|
||||
"${CMAKE_SOURCE_DIR}/3rdparty/taglib/taglib/xm"
|
||||
)
|
||||
|
||||
set(JWT_CPP_INCLUDE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/jwt-cpp/include
|
||||
)
|
||||
|
||||
set (ORM_DIR
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/ormpp
|
||||
)
|
||||
|
||||
include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
|
||||
conan_basic_setup()
|
||||
|
||||
find_library(BCRYPT bcrypt ${BCRYPTLIB})
|
||||
|
||||
include_directories(include ${CPR_INCLUDE_DIRS} ${TAGLIB} ${TAGLIB_HEADERS} ${BCRYPTLIB}/)
|
||||
|
||||
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/cpr)
|
||||
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/taglib)
|
||||
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/oatpp)
|
||||
#add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/libbcrypt)
|
||||
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/appsettings.json ${CMAKE_BINARY_DIR}/bin/appsettings.json COPYONLY)
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/authcredentials.json ${CMAKE_BINARY_DIR}/bin/authcredentials.json COPYONLY)
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/database.json ${CMAKE_BINARY_DIR}/bin/database.json COPYONLY)
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/paths.json ${CMAKE_BINARY_DIR}/bin/paths.json COPYONLY)
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/Images/Stock/CoverArt.png ${CMAKE_BINARY_DIR}/bin/CoverArt.png COPYONLY)
|
||||
|
||||
add_executable(icarus ${SOURCES} ${HEADERS})
|
||||
target_include_directories(icarus PUBLIC ${JWT_CPP_INCLUDE})
|
||||
target_link_libraries(icarus "-lstdc++fs" tag oatpp mysqlclient ${CONAN_LIBS} ${CPR_LIBRARIES} ${BCRYPT})
|
||||
@@ -1,67 +0,0 @@
|
||||
# Contributing
|
||||
|
||||
When contributing to this repository, please first discuss the change you wish to make via issue,
|
||||
email, or any other method with the owners of this repository before making a change.
|
||||
|
||||
Please note the project has a code of conduct, please follow it in all your interactions with the project.
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
### Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, I as
|
||||
aintainers pledge to making participation in this project and
|
||||
this community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, gender identity and expression, level of experience,
|
||||
nationality, personal appearance, race, religion, or sexual identity and
|
||||
orientation.
|
||||
|
||||
### Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
### Maintainers Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## How You Can Contribute
|
||||
|
||||
The project is always looking for people to help in anyway they can. The following would be greatly appreciated:
|
||||
|
||||
* Developers
|
||||
* Graphic Designers
|
||||
* Documenters
|
||||
|
||||
### Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
||||
@@ -0,0 +1,33 @@
|
||||
[package]
|
||||
name = "icarus"
|
||||
version = "0.1.101"
|
||||
edition = "2024"
|
||||
rust-version = "1.88"
|
||||
|
||||
[dependencies]
|
||||
axum = { version = "0.8.4", features = ["multipart"] }
|
||||
axum-extra = { version = "0.10.1", features = ["cookie"] }
|
||||
serde = { version = "1.0.219", features = ["derive"] }
|
||||
serde_json = { version = "1.0.140" }
|
||||
tower = { version = "0.5.2", features = ["full"] }
|
||||
tokio = { version = "1.45.1", features = ["full"] }
|
||||
tokio-util = { version = "0.7.15", features = ["io"] }
|
||||
tower-http = { version = "0.6.6", features = ["cors", "timeout"] }
|
||||
tracing-subscriber = "0.3.19"
|
||||
futures = { version = "0.3.31" }
|
||||
mime_guess = { version = "2.0.5" }
|
||||
uuid = { version = "1.17.0", features = ["v4", "serde"] }
|
||||
sqlx = { version = "0.8.6", features = ["postgres", "runtime-tokio-native-tls", "time", "uuid"] }
|
||||
time = { version = "0.3.41", features = ["formatting", "macros", "parsing", "serde"] }
|
||||
thiserror = "1.0"
|
||||
base64 = "0.21"
|
||||
jsonwebtoken = { version = "9.3.1" }
|
||||
josekit = { version = "0.10.1" }
|
||||
icarus_meta = { git = "ssh://git@git.kundeng.us/phoenix/icarus_meta.git", tag = "v0.3.0-devel-f4b71de969-680" }
|
||||
icarus_models = { git = "ssh://git@git.kundeng.us/phoenix/icarus_models.git", tag = "v0.5.2-devel-d3251f935e-111" }
|
||||
icarus_envy = { git = "ssh://git@git.kundeng.us/phoenix/icarus_envy.git", tag = "v0.3.0-devel-d73fba9899-006" }
|
||||
|
||||
[dev-dependencies]
|
||||
common-multipart-rfc7578 = { version = "0.7.0" }
|
||||
url = { version = "2.5.4" }
|
||||
tempfile = { version = "3.20.0" }
|
||||
@@ -0,0 +1,70 @@
|
||||
# Stage 1: Build the application
|
||||
FROM rust:1.88 as builder
|
||||
|
||||
# Set the working directory inside the container
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
# Install build dependencies if needed (e.g., git for cloning)
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
pkg-config libssl3 \
|
||||
ca-certificates \
|
||||
openssh-client git \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Create .ssh/ directory for internal dependencies
|
||||
RUN mkdir -p -m 0700 ~/.ssh && \
|
||||
echo "Host git.kundeng.us" >> ~/.ssh/config && \
|
||||
echo " User git" >> ~/.ssh/config && \
|
||||
chmod 600 ~/.ssh/config
|
||||
|
||||
# << --- ADD HOST KEY HERE --- >>
|
||||
RUN ssh-keyscan git.kundeng.us >> ~/.ssh/known_hosts
|
||||
|
||||
# Copy Cargo manifests
|
||||
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 && \
|
||||
echo "fn main() {println!(\"if you see this, the build broke\")}" > src/main.rs && \
|
||||
cargo build --release --quiet && \
|
||||
rm -rf src target/release/deps/icarus* # Clean up dummy build artifacts (replace icarus)
|
||||
|
||||
# Copy the actual source code
|
||||
COPY src ./src
|
||||
# If you have other directories like `templates` or `static`, copy them too
|
||||
COPY .env ./.env
|
||||
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 \
|
||||
cargo build --release --quiet
|
||||
|
||||
# Stage 2: Create the final, smaller runtime image
|
||||
# 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)
|
||||
RUN apt-get update && apt-get install -y ca-certificates libssl-dev libssl3 && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Set the working directory
|
||||
WORKDIR /usr/local/bin
|
||||
|
||||
# Copy the compiled binary from the builder stage
|
||||
# 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/migrations ./migrations
|
||||
|
||||
# Expose the port your Axum app listens on (e.g., 3000 or 8000)
|
||||
EXPOSE 3000
|
||||
|
||||
# Set the command to run your application
|
||||
# Ensure this matches the binary name copied above
|
||||
CMD ["./icarus"]
|
||||
|
Before Width: | Height: | Size: 107 KiB |
|
Before Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 123 KiB |
|
Before Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 86 KiB |
|
Before Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 102 KiB |
|
Before Width: | Height: | Size: 24 KiB |
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 Kun Deng
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1,177 +1,17 @@
|
||||
# Icarus
|
||||
|
||||
Icarus is a music streaming API Server, allowing access to stream your personal music collection
|
||||
|
||||
### Interfacing With Icarus
|
||||
|
||||
One can interface with Icarus the music server either by:
|
||||
|
||||
* [Mear](https://github.com/amazing-username/mear) - Partially implemented (under development)
|
||||
* [IcarusDownloadManager](https://github.com/amazing-username/IcarusDownloadManager)
|
||||
* Create your own client to interact with the API
|
||||
|
||||
|
||||
# Getting Started
|
||||
## Docker
|
||||
Make sure `icarus_auth` is located in the root of the parent directory if using docker.
|
||||
|
||||
## Built With
|
||||
Create a `.env` file for both projects - `icarus_auth` and `icarus` - in the root of each project.
|
||||
|
||||
|
||||
* C++ >= C++17
|
||||
* GCC >= 8.0
|
||||
* [json](https://www.github.com/nlohmann/json)
|
||||
* [cpr](https://www.github.com/whoshuu/cpr)
|
||||
* [TagLib](https://github.com/taglib/taglib)
|
||||
* [jwt-cpp](https://github.com/Thalhammer/jwt-cpp)
|
||||
* [libbcrypt](https://github.com/rg3/libbcrypt)
|
||||
* [oatpp](https://github.com/oatpp/oatpp)
|
||||
|
||||

|
||||
|
||||
## Getting started
|
||||
There are several things that need to be completed to properly setup and secure the API.
|
||||
1. Auth0 API configuration
|
||||
2. API filesystem paths
|
||||
3. Database connection string
|
||||
4. Migrations
|
||||
|
||||
### Auth0 API configuration
|
||||
|
||||
Securing Icarus is required, preventing the API from being publicly accessible. To do so, create an Auth0 account (it's free), for the sake of this section of the documentation, I will not go over how to create an Auth0 account. Once created, create a tentant and proceed to create an API
|
||||
<h1 align=center>
|
||||
<img src="Images/Configuration/create_api.png" width=100%>
|
||||
</h1>
|
||||
|
||||
Create the API and enter an approrpiate name and identified. For the identified, append **api** like in the example
|
||||
<h1 align="center">
|
||||
<img src="Images/Configuration/enter_api_info.png" width=100%>
|
||||
</h1>
|
||||
Replace [domain] with the domain name of the created tenant. This can be found in the Default App from the Application menu. Replace [identifier] with the identifer root name in the appsettings environment file. Not the friendly name but the root name of the identifier, omitting the http protocol and the *api* path.
|
||||
|
||||
```Json
|
||||
"domain": "[domain].auth0.com",
|
||||
"api_identifier": "https://[identifier]/api",
|
||||
"client_id": "iamunique",
|
||||
"client_secret": "Icankeepasecret"
|
||||
Build containers
|
||||
```
|
||||
docker compose build --ssh default api auth_api
|
||||
```
|
||||
|
||||
For the sake of this section, I will not go over configuring the API to accept the signing algorithm since it has already been configured in the [Startip](Startup.cs).cs file. Click on permissions to create the permissions for the API.
|
||||
<h1 align "center">
|
||||
<img src="Images/Configuration/configure_api.png" width=100%>
|
||||
</h1>
|
||||
|
||||
The permissions ensure that a validated user can interact with the API with a token that has not expired. Ensure that the permissions match, the description can change but the permission identifier must match.
|
||||
<h1 align="center">
|
||||
<img src="Images/Configuration/permissions.png" width=100%>
|
||||
</h1>
|
||||
|
||||
On the left side, click on Application and create a new Application. Choose the Machine to Machine Application
|
||||
<h1 align="center">
|
||||
<img src="Images/Configuration/create_m2m.png" width=100%>
|
||||
</h1>
|
||||
|
||||
With the grant permissions you created from the API, enable all the permissions. This is important because if they are not enabled then even with a valid token the request will return 403 (unauthorized)
|
||||
<h1 align="center">
|
||||
<img src="Images/Configuration/authorize_app.png" width=100%>
|
||||
</h1>
|
||||
|
||||
From the Application page, copy the client id and client secret. These values will be used for the API to interact with API.
|
||||
<h1 align="center">
|
||||
<img src="Images/Configuration/api_cred.png" width=100%>
|
||||
</h1>
|
||||
Enter the information in the corresponding ``authcredentials.json`` file
|
||||
```Json
|
||||
{
|
||||
"domain": "somedomain.auth0.com",
|
||||
"api_identifier": "https://squawk/api"
|
||||
"client_id": "clientidhere",
|
||||
"client_secret": "illkeepyoumydirtylittlesecret"
|
||||
}
|
||||
Bring it up
|
||||
```
|
||||
|
||||
### API filesystem paths
|
||||
|
||||
For the purposes of properly uploading, downloading, updating, deleting, and streaming songs the API filesystem paths must be configured. For that purpose you have to open the ``paths.json`` file. What is meant by this is that the `root_music_path` directory where all music will be stored must exist. The `cover_root_path`, `archive_root_path`, and `temp_root_path` paths must exist and be accessible. An example on a Linux system:
|
||||
```Json
|
||||
{
|
||||
"root_music_path": "/dev/null/music/",
|
||||
"temp_root_path": "/dev/null/music/temp/",
|
||||
"cover_root_path": "/dev/null/music/coverArt/",
|
||||
"archive_root_path": "/dev/null/music/archive/"
|
||||
}
|
||||
docker compose up -d --force-recreate api auth_api
|
||||
```
|
||||
* `root_music_path` - Where music will be stored in the following convention: *`Artist/Album/Songs`*
|
||||
* `temp_music_path` - Where music will be stored when uploding songs to the server until the metadata has been fully parsed and entered into the database. Upon completion the files will be deleted and moved to the appropriate path in the `root_music_path`
|
||||
* `cover_root_path` - Where cover art of music will be saved to.
|
||||
* `archive_root_path` - When downloading compressed songs this is the path where songs will be compressed prior to dataa being read into memory, deleting the compressed file, and sending the compressed file from memory to the client
|
||||
|
||||
|
||||
**Note**: The `temp_root_path`, `cover_root_path`, or `archive_root_path` does not have to be located in the same parent directory as `root_music_path`. Ensure that the permissions are properly set for all of the paths.
|
||||
|
||||
### Database connection string
|
||||
|
||||
In order for Database functionality to be operable, there must be a valid connection string and MySQL credentials with appropriate permissions. **At the moment there is only support for MySQL**. Edit the database.json file accordingly. An example of the fields to change are below:
|
||||
```Json
|
||||
{
|
||||
|
||||
"server": "localhost",
|
||||
"database": "my_db",
|
||||
"username": "admin",
|
||||
"password": "toughpassword"
|
||||
|
||||
}
|
||||
```
|
||||
* server - The address or domain name of the MySQL server
|
||||
* database - The database name
|
||||
* username - Username
|
||||
* password - Self-explanatory
|
||||
|
||||
The only requirement of the User is that the user should have full permissions to the database as well as permissions to create a database. Other than that, that is all that is required.
|
||||
|
||||
### Database
|
||||
|
||||
Prior to starting the API, the database must be created. The following tables are required:
|
||||
* User
|
||||
* Salt
|
||||
* Song
|
||||
* Album
|
||||
* Artist
|
||||
* Year
|
||||
* Genre
|
||||
* CoverArt
|
||||
|
||||
There is a MySQL script to create these tables, it can be found in the [Scripts/MySQL/](https://github.com/amazing-username/Icarus/blob/master/Scripts/MySQL/create_database.sql) directory. Just merely execute:
|
||||
```shell
|
||||
mysql -u dblikedecibel -p < Scripts/MySQL/create_database.sql
|
||||
```
|
||||
|
||||
From this point the database has been successfully created. Metadata and song filesystem locations can be saved.
|
||||
|
||||
|
||||
## Building and Running
|
||||
```
|
||||
git clone --recursive https://github.com/kdeng00/icarus
|
||||
cd 3rdparty/libbcrypt/
|
||||
make
|
||||
cp bcrypt.a libbcrypt.a
|
||||
cp bcrypt.o libbcrypt.o
|
||||
cd ../..
|
||||
mkdir build
|
||||
cd build
|
||||
conan install ..
|
||||
cmake .. -DCMAKE_BUILD_TYPE=RELEASE -DUSE_SYSTEM_CURL=ON -DBUILD_CPR_TESTS=OFF -DOATPP_BUILD_TESTS=OFF
|
||||
make
|
||||
bin/icarus
|
||||
```
|
||||
Runs the server on localhost port 5002
|
||||
|
||||
## Contributing
|
||||
|
||||
Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details on the code of conduct, and the process for submitting pull requests to the project.
|
||||
|
||||
## Authors
|
||||
|
||||
* **Kun Deng** - [amazing-username](https://github.com/amazing-username)
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details
|
||||
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
echo "Adding migrations..."
|
||||
echo "Adding User migration"
|
||||
dotnet ef migrations add User --context UserContext
|
||||
echo "Adding Song migration"
|
||||
dotnet ef migrations add Song --context SongContext
|
||||
echo "Adding Album migration"
|
||||
dotnet ef migrations add Album --context AlbumContext
|
||||
echo "Adding Artist migration"
|
||||
dotnet ef migrations add Artist --context ArtistContext
|
||||
echo "Adding Genre migration"
|
||||
dotnet ef migrations add Genre --context GenreContext
|
||||
echo "Adding Year migration"
|
||||
dotnet ef migrations add Year --context YearContext
|
||||
echo "Adding Cover art migration"
|
||||
dotnet ef migrations add CoverArt --context CoverArtContext
|
||||
|
||||
echo "Updating migrations.."
|
||||
echo "Updating User migration"
|
||||
dotnet ef database update --context UserContext
|
||||
echo "Updating Song migration"
|
||||
echo "Updating Album migration"
|
||||
echo "Updating Artist migration"
|
||||
echo "Updating Genre migration"
|
||||
echo "Updating Year migration"
|
||||
echo "Updating Cover art migration"
|
||||
dotnet ef database update --context SongContext
|
||||
@@ -1,86 +0,0 @@
|
||||
CREATE DATABASE Icarus;
|
||||
|
||||
USE Icarus;
|
||||
|
||||
CREATE TABLE CoverArt (
|
||||
CoverArtId INT NOT NULL AUTO_INCREMENT,
|
||||
SongTitle TEXT NOT NULL,
|
||||
ImagePath TEXT NOT NULL,
|
||||
|
||||
PRIMARY KEY (CoverArtId)
|
||||
);
|
||||
|
||||
CREATE TABLE Album (
|
||||
AlbumId INT NOT NULL AUTO_INCREMENT,
|
||||
Title TEXT NOT NULL,
|
||||
Year INT NOT NULL,
|
||||
|
||||
PRIMARY KEY (AlbumId)
|
||||
);
|
||||
|
||||
CREATE TABLE Artist (
|
||||
ArtistId INT NOT NULL AUTO_INCREMENT,
|
||||
Artist TEXT NOT NULL,
|
||||
|
||||
PRIMARY KEY (ArtistId)
|
||||
);
|
||||
|
||||
CREATE TABLE Genre (
|
||||
GenreId INT NOT NULL AUTO_INCREMENT,
|
||||
Category TEXT NOT NULL,
|
||||
|
||||
PRIMARY KEY (GenreId)
|
||||
);
|
||||
|
||||
CREATE TABLE Year (
|
||||
YearId INT NOT NULL AUTO_INCREMENT,
|
||||
Year INT NOT NULL,
|
||||
|
||||
PRIMARY KEY (YearId)
|
||||
);
|
||||
|
||||
CREATE TABLE Song (
|
||||
SongId INT NOT NULL AUTO_INCREMENT,
|
||||
Title TEXT NOT NULL,
|
||||
Artist TEXT NOT NULL,
|
||||
Album TEXT NOT NULL,
|
||||
Genre TEXT NOT NULL,
|
||||
Year INT NOT NULL,
|
||||
Duration INT NOT NULL,
|
||||
Track INT NOT NULL,
|
||||
Disc INT NOT NULL,
|
||||
SongPath TEXT NOT NULL,
|
||||
CoverArtId INT NOT NULL,
|
||||
ArtistId INT NOT NULL,
|
||||
AlbumId INT NOT NULL,
|
||||
GenreId INT NOT NULL,
|
||||
YearId INT NOT NULL,
|
||||
|
||||
PRIMARY KEY (SongId),
|
||||
CONSTRAINT FK_CoverArtId FOREIGN KEY (CoverArtId) REFERENCES CoverArt (CoverArtId),
|
||||
CONSTRAINT FK_ArtistId FOREIGN KEY (ArtistId) REFERENCES Artist (ArtistId),
|
||||
CONSTRAINT FK_AlbumId FOREIGN KEY (AlbumId) REFERENCES Album (AlbumId),
|
||||
CONSTRAINT FK_GenreId FOREIGN KEY (GenreId) REFERENCES Genre (GenreId),
|
||||
CONSTRAINT FK_YearId FOREIGN KEY (YearId) REFERENCES Year (YearId)
|
||||
);
|
||||
|
||||
CREATE TABLE User (
|
||||
UserId INT NOT NULL AUTO_INCREMENT,
|
||||
Firstname TEXT NOT NULL,
|
||||
Lastname TEXT NOT NULL,
|
||||
Email TEXT NOT NULL,
|
||||
Phone TEXT NOT NULL,
|
||||
Username TEXT NOT NULL,
|
||||
Password TEXT NOT NULL,
|
||||
|
||||
PRIMARY KEY (UserId)
|
||||
);
|
||||
|
||||
CREATE TABLE Salt (
|
||||
SaltId INT NOT NULL AUTO_INCREMENT,
|
||||
Salt TEXT NOT NULL,
|
||||
UserId INT NOT NULL,
|
||||
|
||||
PRIMARY KEY (SaltId),
|
||||
CONSTRAINT FK_UserId FOREIGN KEY (UserId) REFERENCES User (UserId)
|
||||
);
|
||||
@@ -1,6 +0,0 @@
|
||||
delete from Song;
|
||||
delete from Album;
|
||||
delete from Artist;
|
||||
delete from Genre;
|
||||
delete from Year;
|
||||
delete from CoverArt;
|
||||
@@ -1 +0,0 @@
|
||||
select * from Song;
|
||||
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Trace",
|
||||
"Microsoft": "Information"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"Auth0": {
|
||||
"Domain": "[domain].auth0.com",
|
||||
"ApiIdentifier": "https://[identifier]/api",
|
||||
"ClientId": "",
|
||||
"ClientSecret": ""
|
||||
},
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": "Server=;Database=;Uid=;Pwd=;"
|
||||
},
|
||||
"RootMusicPath": "/music/path/",
|
||||
"TemporaryMusicPath": "/music/temp/path/",
|
||||
"ArchivePath": "/archive/path/",
|
||||
"CoverArtPath": "/cover/art/path/"
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"domain": "[domain].auth0.com",
|
||||
"api_identifier": "https://[identifier]/api",
|
||||
"client_id": "dfdfdfdf",
|
||||
"client_secret": "dfdfdfdf"
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
[requires]
|
||||
jsonformoderncpp/3.7.0@vthiery/stable
|
||||
|
||||
[generators]
|
||||
cmake
|
||||
@@ -1,7 +0,0 @@
|
||||
|
||||
{
|
||||
"server": "localhost",
|
||||
"database": "Icarus",
|
||||
"username": "icarus-admin",
|
||||
"password": "dreamofpeace"
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
version: '3.8' # Use a recent version
|
||||
|
||||
services:
|
||||
# --- Web API ---
|
||||
api:
|
||||
build: # Tells docker-compose to build the Dockerfile in the current directory
|
||||
context: .
|
||||
ssh: ["default"] # Uses host's SSH agent
|
||||
container_name: icarus # Optional: Give the container a specific name
|
||||
ports:
|
||||
# Map host port 8000 to container port 3000 (adjust as needed)
|
||||
# Format: "HOST_PORT:CONTAINER_PORT"
|
||||
- "8000:3000"
|
||||
env_file:
|
||||
- .env
|
||||
depends_on:
|
||||
main_db:
|
||||
condition: service_healthy
|
||||
networks:
|
||||
- icarus-network
|
||||
restart: unless-stopped # Optional: Restart policy
|
||||
|
||||
# --- Web API auth ---
|
||||
auth_api:
|
||||
build:
|
||||
context: ../icarus_auth # IMPORTANT: Relative path to the local checkout of your web API repo (containing the Dockerfile)
|
||||
ssh: ["default"] # Uses host's SSH agent
|
||||
dockerfile: Dockerfile # Optional: Specify if your Dockerfile has a non-standard name
|
||||
container_name: auth_api
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "8001:3000" # Map host port 8000 to container port 8000 (adjust container port based on your app's EXPOSE in Dockerfile)
|
||||
# environment:
|
||||
# Environment variables your API needs, e.g., database connection
|
||||
# Add other necessary environment variables
|
||||
env_file:
|
||||
- ../icarus_auth/.env
|
||||
depends_on:
|
||||
auth_db:
|
||||
condition: service_healthy
|
||||
networks:
|
||||
- icarus-network
|
||||
# Optional: Mount local code for development (live reload)
|
||||
# volumes:
|
||||
# - ./path/to/your/web-api-repo:/app
|
||||
|
||||
# --- songparser service ---
|
||||
songparser:
|
||||
build:
|
||||
context: ../songparser
|
||||
ssh: ["default"]
|
||||
dockerfile: Dockerfile
|
||||
container_name: songparser
|
||||
restart: unless-stopped
|
||||
env_file:
|
||||
- ../songparser/.env
|
||||
depends_on:
|
||||
- api
|
||||
- main_db
|
||||
- auth_api
|
||||
- auth_db
|
||||
networks:
|
||||
- icarus-network
|
||||
|
||||
|
||||
# PostgreSQL Database Service
|
||||
# --- icarus web api db ---
|
||||
main_db:
|
||||
image: postgres:17.5-alpine # Use an official Postgres image (Alpine variant is smaller)
|
||||
container_name: icarus_db # Optional: Give the container a specific name
|
||||
environment:
|
||||
# These MUST match the user, password, and database name in the DATABASE_URL above
|
||||
POSTGRES_USER: ${POSTGRES_MAIN_USER:-icarus}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_MAIN_PASSWORD:-password}
|
||||
POSTGRES_DB: ${POSTGRES_MAIN_DB:-icarus_db}
|
||||
volumes:
|
||||
# Persist database data using a named volume
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
ports:
|
||||
# Optional: Expose port 5432 ONLY if you need to connect directly from your host machine (e.g., for debugging)
|
||||
- "5432:5432"
|
||||
healthcheck:
|
||||
# Checks if Postgres is ready to accept connections
|
||||
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 10s
|
||||
restart: always # Optional: Restart policy
|
||||
networks:
|
||||
- icarus-network
|
||||
|
||||
# --- icarus web auth api db ---
|
||||
auth_db:
|
||||
image: postgres:17.5-alpine # Use an official Postgres image (Alpine variant is smaller)
|
||||
container_name: icarus_auth_db # Optional: Give the container a specific name
|
||||
environment:
|
||||
# These MUST match the user, password, and database name in the DATABASE_URL above
|
||||
POSTGRES_USER: ${POSTGRES_AUTH_USER:-icarus_op}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_AUTH_PASSWORD:-password}
|
||||
POSTGRES_DB: ${POSTGRES_AUTH_DB:-icarus_auth_db}
|
||||
volumes:
|
||||
# Persist database data using a named volume
|
||||
- postgres_data_auth:/var/lib/postgresql/data
|
||||
ports:
|
||||
# Optional: Expose port 5432 ONLY if you need to connect directly from your host machine (e.g., for debugging)
|
||||
- "5433:5432"
|
||||
healthcheck:
|
||||
# Checks if Postgres is ready to accept connections
|
||||
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 10s
|
||||
restart: always # Optional: Restart policy
|
||||
networks:
|
||||
- icarus-network
|
||||
|
||||
# Define the named volume for data persistence
|
||||
volumes:
|
||||
postgres_data:
|
||||
driver: local # Use the default local driver
|
||||
postgres_data_auth:
|
||||
driver: local # Use the default local driver
|
||||
|
||||
# Define the network (optional, but good practice)
|
||||
networks:
|
||||
icarus-network:
|
||||
driver: bridge
|
||||
@@ -1,26 +0,0 @@
|
||||
#ifndef STREAMCALLBACK_H_
|
||||
#define STREAMCALLBACK_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "oatpp/core/data/stream/FileStream.hpp"
|
||||
#include "oatpp/web/protocol/http/outgoing/ChunkedBody.hpp"
|
||||
|
||||
namespace callback {
|
||||
class StreamCallback : public oatpp::data::stream::ReadCallback
|
||||
{
|
||||
public:
|
||||
StreamCallback();
|
||||
StreamCallback(const std::string&);
|
||||
|
||||
oatpp::data::v_io_size read(void*, oatpp::data::v_io_size);
|
||||
private:
|
||||
std::string m_songPath;
|
||||
|
||||
long m_bytesRead;
|
||||
long m_counter;
|
||||
long m_fileSize;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,37 +0,0 @@
|
||||
#ifndef APPCOMPONENT_H_
|
||||
#define APPCOMPONENT_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "oatpp/core/macro/component.hpp"
|
||||
#include "oatpp/network/server/SimpleTCPConnectionProvider.hpp"
|
||||
#include "oatpp/parser/json/mapping/ObjectMapper.hpp"
|
||||
#include "oatpp/web/server/HttpConnectionHandler.hpp"
|
||||
|
||||
namespace component {
|
||||
|
||||
class AppComponent
|
||||
{
|
||||
public:
|
||||
OATPP_CREATE_COMPONENT(std::shared_ptr<oatpp::network::ServerConnectionProvider>, serverConnectionProvider)([] {
|
||||
return oatpp::network::server::SimpleTCPConnectionProvider::createShared(5002);
|
||||
}());
|
||||
|
||||
OATPP_CREATE_COMPONENT(std::shared_ptr<oatpp::web::server::HttpRouter>, httpRouter)([] {
|
||||
return oatpp::web::server::HttpRouter::createShared();
|
||||
}());
|
||||
|
||||
OATPP_CREATE_COMPONENT(std::shared_ptr<oatpp::network::server::ConnectionHandler>, serverConnectionHandler)([] {
|
||||
OATPP_COMPONENT(std::shared_ptr<oatpp::web::server::HttpRouter>, router);
|
||||
|
||||
return oatpp::web::server::HttpConnectionHandler::createShared(router);
|
||||
}());
|
||||
|
||||
OATPP_CREATE_COMPONENT(std::shared_ptr<oatpp::data::mapping::ObjectMapper>, apiObjectMapper)([] {
|
||||
return oatpp::parser::json::mapping::ObjectMapper::createShared();
|
||||
}());
|
||||
private:
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,102 +0,0 @@
|
||||
#ifndef ALBUMCONTROLLER_H_
|
||||
#define ALBUMCONTROLLER_H_
|
||||
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "oatpp/core/data/stream/ChunkedBuffer.hpp"
|
||||
#include "oatpp/core/data/stream/FileStream.hpp"
|
||||
#include "oatpp/core/macro/codegen.hpp"
|
||||
#include "oatpp/core/macro/component.hpp"
|
||||
#include "oatpp/web/mime/multipart/InMemoryPartReader.hpp"
|
||||
#include "oatpp/web/mime/multipart/Reader.hpp"
|
||||
#include "oatpp/web/server/api/ApiController.hpp"
|
||||
|
||||
#include "database/AlbumRepository.h"
|
||||
#include "dto/AlbumDto.hpp"
|
||||
#include "manager/AlbumManager.h"
|
||||
#include "manager/TokenManager.h"
|
||||
#include "model/Models.h"
|
||||
#include "type/Scopes.h"
|
||||
#include "type/AlbumFilter.h"
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace controller {
|
||||
class AlbumController : public oatpp::web::server::api::ApiController
|
||||
{
|
||||
public:
|
||||
AlbumController(std::string p, OATPP_COMPONENT(std::shared_ptr<ObjectMapper>, objectMapper))
|
||||
: oatpp::web::server::api::ApiController(objectMapper), m_exe_path(p)
|
||||
{ }
|
||||
|
||||
AlbumController(const model::BinaryPath& bConf, OATPP_COMPONENT(std::shared_ptr<ObjectMapper>, objectMapper))
|
||||
: oatpp::web::server::api::ApiController(objectMapper), m_bConf(bConf)
|
||||
{ }
|
||||
|
||||
#include OATPP_CODEGEN_BEGIN(ApiController)
|
||||
|
||||
// endpoint for retrieving all album records in json format
|
||||
ENDPOINT("GET", "/api/v1/album", albumRecords,
|
||||
REQUEST(std::shared_ptr<IncomingRequest>, request))
|
||||
{
|
||||
auto authHeader = request->getHeader("Authorization");
|
||||
OATPP_ASSERT_HTTP(authHeader, Status::CODE_403, "Nope");
|
||||
auto auth = authHeader->std_str();
|
||||
manager::TokenManager tok;
|
||||
OATPP_ASSERT_HTTP(tok.isTokenValid(auth, type::Scope::retrieveAlbum), Status::CODE_403, "Not allowed");
|
||||
|
||||
std::cout << "starting process of retrieving album" << std::endl;
|
||||
database::AlbumRepository albRepo(m_bConf);
|
||||
auto albsDb = albRepo.retrieveRecords();
|
||||
auto albums = oatpp::data::mapping::type::List<dto::AlbumDto::ObjectWrapper>::createShared();
|
||||
|
||||
for (auto& albDb : albsDb) {
|
||||
auto alb = dto::AlbumDto::createShared();
|
||||
alb->id = albDb.id;
|
||||
alb->title = albDb.title.c_str();
|
||||
alb->year = albDb.year;
|
||||
|
||||
albums->pushBack(alb);
|
||||
}
|
||||
|
||||
return createDtoResponse(Status::CODE_200, albums);
|
||||
}
|
||||
|
||||
// endpoint for retrieving single album record by the album id in json format
|
||||
ENDPOINT("GET", "/api/v1/album/{id}", albumRecord,
|
||||
REQUEST(std::shared_ptr<IncomingRequest>, request), PATH(Int32, id)) {
|
||||
auto authHeader = request->getHeader("Authorization");
|
||||
OATPP_ASSERT_HTTP(authHeader, Status::CODE_403, "Nope");
|
||||
auto auth = authHeader->std_str();
|
||||
manager::TokenManager tok;
|
||||
OATPP_ASSERT_HTTP(tok.isTokenValid(auth, type::Scope::retrieveAlbum), Status::CODE_403, "Not allowed");
|
||||
|
||||
database::AlbumRepository albRepo(m_bConf);
|
||||
model::Album albDb(id);
|
||||
|
||||
OATPP_ASSERT_HTTP(albRepo.doesAlbumExists(albDb, type::AlbumFilter::id) , Status::CODE_403, "album does not exist");
|
||||
|
||||
std::cout << "album exists" << std::endl;
|
||||
albDb = albRepo.retrieveRecord(albDb, type::AlbumFilter::id);
|
||||
|
||||
auto album = dto::AlbumDto::createShared();
|
||||
album->id = albDb.id;
|
||||
album->title = albDb.title.c_str();
|
||||
album->year = albDb.year;
|
||||
|
||||
return createDtoResponse(Status::CODE_200, album);
|
||||
}
|
||||
|
||||
#include OATPP_CODEGEN_END(ApiController)
|
||||
private:
|
||||
std::string m_exe_path;
|
||||
model::BinaryPath m_bConf;
|
||||
};
|
||||
}
|
||||
#endif
|
||||
@@ -1,100 +0,0 @@
|
||||
#ifndef ARTISTCONTROLLER_H_
|
||||
#define ARTISTCONTROLLER_H_
|
||||
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "oatpp/core/data/stream/ChunkedBuffer.hpp"
|
||||
#include "oatpp/core/data/stream/FileStream.hpp"
|
||||
#include "oatpp/core/macro/codegen.hpp"
|
||||
#include "oatpp/core/macro/component.hpp"
|
||||
#include "oatpp/web/mime/multipart/InMemoryPartReader.hpp"
|
||||
#include "oatpp/web/mime/multipart/Reader.hpp"
|
||||
#include "oatpp/web/server/api/ApiController.hpp"
|
||||
|
||||
#include "database/ArtistRepository.h"
|
||||
#include "dto/ArtistDto.hpp"
|
||||
#include "manager/ArtistManager.h"
|
||||
#include "manager/TokenManager.h"
|
||||
#include "model/Models.h"
|
||||
#include "type/Scopes.h"
|
||||
#include "type/ArtistFilter.h"
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace controller {
|
||||
class ArtistController : public oatpp::web::server::api::ApiController
|
||||
{
|
||||
public:
|
||||
ArtistController(std::string p, OATPP_COMPONENT(std::shared_ptr<ObjectMapper>, objectMapper))
|
||||
: oatpp::web::server::api::ApiController(objectMapper), m_exe_path(p)
|
||||
{ }
|
||||
|
||||
ArtistController(const model::BinaryPath& bConf, OATPP_COMPONENT(std::shared_ptr<ObjectMapper>, objectMapper))
|
||||
: oatpp::web::server::api::ApiController(objectMapper), m_bConf(bConf)
|
||||
{ }
|
||||
|
||||
#include OATPP_CODEGEN_BEGIN(ApiController)
|
||||
|
||||
// endpoint for retrieving all artist records in json format
|
||||
ENDPOINT("GET", "/api/v1/artist", artistRecords,
|
||||
REQUEST(std::shared_ptr<IncomingRequest>, request))
|
||||
{
|
||||
auto authHeader = request->getHeader("Authorization");
|
||||
OATPP_ASSERT_HTTP(authHeader, Status::CODE_403, "Nope");
|
||||
auto auth = authHeader->std_str();
|
||||
manager::TokenManager tok;
|
||||
OATPP_ASSERT_HTTP(tok.isTokenValid(auth, type::Scope::retrieveArtist), Status::CODE_403, "Not allowed");
|
||||
|
||||
std::cout << "starting process of retrieving artist" << std::endl;
|
||||
database::ArtistRepository artRepo(m_bConf);
|
||||
auto artsDb = artRepo.retrieveRecords();
|
||||
auto artists = oatpp::data::mapping::type::List<dto::ArtistDto::ObjectWrapper>::createShared();
|
||||
|
||||
for (auto& artDb : artsDb) {
|
||||
auto art = dto::ArtistDto::createShared();
|
||||
art->id = artDb.id;
|
||||
art->artist = artDb.artist.c_str();
|
||||
|
||||
artists->pushBack(art);
|
||||
}
|
||||
|
||||
return createDtoResponse(Status::CODE_200, artists);
|
||||
}
|
||||
|
||||
// endpoint for retrieving single artist record by the artist id in json format
|
||||
ENDPOINT("GET", "/api/v1/artist/{id}", artistRecord,
|
||||
REQUEST(std::shared_ptr<IncomingRequest>, request), PATH(Int32, id)) {
|
||||
auto authHeader = request->getHeader("Authorization");
|
||||
OATPP_ASSERT_HTTP(authHeader, Status::CODE_403, "Nope");
|
||||
auto auth = authHeader->std_str();
|
||||
manager::TokenManager tok;
|
||||
OATPP_ASSERT_HTTP(tok.isTokenValid(auth, type::Scope::retrieveArtist), Status::CODE_403, "Not allowed");
|
||||
|
||||
database::ArtistRepository artRepo(m_bConf);
|
||||
model::Artist artDb(id);
|
||||
|
||||
OATPP_ASSERT_HTTP(artRepo.doesArtistExist(artDb, type::ArtistFilter::id) , Status::CODE_403, "artist does not exist");
|
||||
|
||||
std::cout << "artist exist" << std::endl;
|
||||
artDb = artRepo.retrieveRecord(artDb, type::ArtistFilter::id);
|
||||
|
||||
auto artist = dto::ArtistDto::createShared();
|
||||
artist->id = artDb.id;
|
||||
artist->artist = artDb.artist.c_str();
|
||||
|
||||
return createDtoResponse(Status::CODE_200, artist);
|
||||
}
|
||||
|
||||
#include OATPP_CODEGEN_END(ApiController)
|
||||
private:
|
||||
std::string m_exe_path;
|
||||
model::BinaryPath m_bConf;
|
||||
};
|
||||
}
|
||||
#endif
|
||||
@@ -1,121 +0,0 @@
|
||||
#ifndef COVERARTCONTROLLER_H_
|
||||
#define COVERARTCONTROLLER_H_
|
||||
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "oatpp/core/data/stream/ChunkedBuffer.hpp"
|
||||
#include "oatpp/core/data/stream/FileStream.hpp"
|
||||
#include "oatpp/core/macro/codegen.hpp"
|
||||
#include "oatpp/core/macro/component.hpp"
|
||||
#include "oatpp/web/mime/multipart/InMemoryPartReader.hpp"
|
||||
#include "oatpp/web/mime/multipart/Reader.hpp"
|
||||
#include "oatpp/web/server/api/ApiController.hpp"
|
||||
|
||||
#include "database/CoverArtRepository.h"
|
||||
#include "dto/CoverArtDto.hpp"
|
||||
#include "manager/CoverArtManager.h"
|
||||
#include "model/Models.h"
|
||||
#include "type/Scopes.h"
|
||||
#include "type/CoverFilter.h"
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace controller {
|
||||
class CoverArtController : public oatpp::web::server::api::ApiController
|
||||
{
|
||||
public:
|
||||
CoverArtController(std::string p, OATPP_COMPONENT(std::shared_ptr<ObjectMapper>, objectMapper))
|
||||
: oatpp::web::server::api::ApiController(objectMapper), m_exe_path(p)
|
||||
{ }
|
||||
|
||||
CoverArtController(const model::BinaryPath& bConf, OATPP_COMPONENT(std::shared_ptr<ObjectMapper>, objectMapper))
|
||||
: oatpp::web::server::api::ApiController(objectMapper), m_bConf(bConf)
|
||||
{ }
|
||||
|
||||
#include OATPP_CODEGEN_BEGIN(ApiController)
|
||||
|
||||
// endpoint for retrieving all cover art records in json format
|
||||
ENDPOINT("GET", "/api/v1/coverart", coverArtRecords,
|
||||
REQUEST(std::shared_ptr<IncomingRequest>, request))
|
||||
{
|
||||
auto authHeader = request->getHeader("Authorization");
|
||||
OATPP_ASSERT_HTTP(authHeader, Status::CODE_403, "Nope");
|
||||
auto auth = authHeader->std_str();
|
||||
manager::TokenManager tok;
|
||||
OATPP_ASSERT_HTTP(tok.isTokenValid(auth, type::Scope::downloadCoverArt), Status::CODE_403, "Not allowed");
|
||||
|
||||
std::cout << "starting process of retrieving cover art" << std::endl;
|
||||
database::CoverArtRepository covRepo(m_bConf);
|
||||
auto covsDb = covRepo.retrieveRecords();
|
||||
auto coverArts = oatpp::data::mapping::type::List<dto::CoverArtDto::ObjectWrapper>::createShared();
|
||||
|
||||
for (auto& covDb : covsDb) {
|
||||
auto cov = dto::CoverArtDto::createShared();
|
||||
cov->id = covDb.id;
|
||||
cov->songTitle = covDb.songTitle.c_str();
|
||||
|
||||
coverArts->pushBack(cov);
|
||||
}
|
||||
|
||||
return createDtoResponse(Status::CODE_200, coverArts);
|
||||
}
|
||||
|
||||
// endpoint for retrieving single cover art record by the cover art id in json format
|
||||
ENDPOINT("GET", "/api/v1/coverart/{id}", coverArtRecord,
|
||||
REQUEST(std::shared_ptr<IncomingRequest>, request), PATH(Int32, id)) {
|
||||
auto authHeader = request->getHeader("Authorization");
|
||||
OATPP_ASSERT_HTTP(authHeader, Status::CODE_403, "Nope");
|
||||
auto auth = authHeader->std_str();
|
||||
manager::TokenManager tok;
|
||||
OATPP_ASSERT_HTTP(tok.isTokenValid(auth, type::Scope::downloadCoverArt), Status::CODE_403, "Not allowed");
|
||||
|
||||
database::CoverArtRepository covRepo(m_bConf);
|
||||
model::Cover covDb;
|
||||
covDb.id = id;
|
||||
|
||||
OATPP_ASSERT_HTTP(covRepo.doesCoverArtExist(covDb, type::CoverFilter::id) , Status::CODE_403, "song does not exist");
|
||||
|
||||
std::cout << "cover art exists" << std::endl;
|
||||
covDb = covRepo.retrieveRecord(covDb, type::CoverFilter::id);
|
||||
|
||||
auto coverArt = dto::CoverArtDto::createShared();
|
||||
coverArt->id = covDb.id;
|
||||
coverArt->songTitle = covDb.songTitle.c_str();
|
||||
|
||||
return createDtoResponse(Status::CODE_200, coverArt);
|
||||
}
|
||||
|
||||
ENDPOINT("GET", "/api/v1/coverart/download/{id}", downloadCoverArt,
|
||||
REQUEST(std::shared_ptr<IncomingRequest>, request), PATH(Int32, id)) {
|
||||
auto authHeader = request->getHeader("Authorization");
|
||||
OATPP_ASSERT_HTTP(authHeader, Status::CODE_403, "Nope");
|
||||
auto auth = authHeader->std_str();
|
||||
manager::TokenManager tok;
|
||||
OATPP_ASSERT_HTTP(tok.isTokenValid(auth, type::Scope::downloadCoverArt), Status::CODE_403, "Not allowed");
|
||||
|
||||
database::CoverArtRepository covRepo(m_bConf);
|
||||
model::Cover covDb;
|
||||
covDb.id = id;
|
||||
covDb = covRepo.retrieveRecord(covDb, type::CoverFilter::id);
|
||||
|
||||
auto rawCover = oatpp::base::StrBuffer::loadFromFile(covDb.imagePath.c_str());
|
||||
auto response = createResponse(Status::CODE_200, rawCover);
|
||||
|
||||
response->putHeader(Header::CONTENT_TYPE, "image/*");
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
#include OATPP_CODEGEN_END(ApiController)
|
||||
private:
|
||||
std::string m_exe_path;
|
||||
model::BinaryPath m_bConf;
|
||||
};
|
||||
}
|
||||
#endif
|
||||
@@ -1,100 +0,0 @@
|
||||
#ifndef GENRECONTROLLER_H_
|
||||
#define GENRECONTROLLER_H_
|
||||
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "oatpp/core/data/stream/ChunkedBuffer.hpp"
|
||||
#include "oatpp/core/data/stream/FileStream.hpp"
|
||||
#include "oatpp/core/macro/codegen.hpp"
|
||||
#include "oatpp/core/macro/component.hpp"
|
||||
#include "oatpp/web/mime/multipart/InMemoryPartReader.hpp"
|
||||
#include "oatpp/web/mime/multipart/Reader.hpp"
|
||||
#include "oatpp/web/server/api/ApiController.hpp"
|
||||
|
||||
#include "database/GenreRepository.h"
|
||||
#include "dto/GenreDto.hpp"
|
||||
#include "manager/GenreManager.h"
|
||||
#include "manager/YearManager.h"
|
||||
#include "model/Models.h"
|
||||
#include "type/Scopes.h"
|
||||
#include "type/GenreFilter.h"
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace controller {
|
||||
class GenreController : public oatpp::web::server::api::ApiController
|
||||
{
|
||||
public:
|
||||
GenreController(std::string p, OATPP_COMPONENT(std::shared_ptr<ObjectMapper>, objectMapper))
|
||||
: oatpp::web::server::api::ApiController(objectMapper), m_exe_path(p)
|
||||
{ }
|
||||
|
||||
GenreController(const model::BinaryPath& bConf, OATPP_COMPONENT(std::shared_ptr<ObjectMapper>, objectMapper))
|
||||
: oatpp::web::server::api::ApiController(objectMapper), m_bConf(bConf)
|
||||
{ }
|
||||
|
||||
#include OATPP_CODEGEN_BEGIN(ApiController)
|
||||
|
||||
// endpoint for retrieving all genre records in json format
|
||||
ENDPOINT("GET", "/api/v1/genre", genreRecords,
|
||||
REQUEST(std::shared_ptr<IncomingRequest>, request))
|
||||
{
|
||||
auto authHeader = request->getHeader("Authorization");
|
||||
OATPP_ASSERT_HTTP(authHeader, Status::CODE_403, "Nope");
|
||||
auto auth = authHeader->std_str();
|
||||
manager::TokenManager tok;
|
||||
OATPP_ASSERT_HTTP(tok.isTokenValid(auth, type::Scope::retrieveGenre), Status::CODE_403, "Not allowed");
|
||||
|
||||
std::cout << "starting process of retrieving genre" << std::endl;
|
||||
database::GenreRepository gnrRepo(m_bConf);
|
||||
auto gnrsDb = gnrRepo.retrieveRecords();
|
||||
auto genres = oatpp::data::mapping::type::List<dto::GenreDto::ObjectWrapper>::createShared();
|
||||
|
||||
for (auto& gnrDb : gnrsDb) {
|
||||
auto gnr = dto::GenreDto::createShared();
|
||||
gnr->id = gnrDb.id;
|
||||
gnr->category = gnrDb.category.c_str();
|
||||
|
||||
genres->pushBack(gnr);
|
||||
}
|
||||
|
||||
return createDtoResponse(Status::CODE_200, genres);
|
||||
}
|
||||
|
||||
// endpoint for retrieving single genre record by the genre id in json format
|
||||
ENDPOINT("GET", "/api/v1/genre/{id}", genreRecord,
|
||||
REQUEST(std::shared_ptr<IncomingRequest>, request), PATH(Int32, id)) {
|
||||
auto authHeader = request->getHeader("Authorization");
|
||||
OATPP_ASSERT_HTTP(authHeader, Status::CODE_403, "Nope");
|
||||
auto auth = authHeader->std_str();
|
||||
manager::TokenManager tok;
|
||||
OATPP_ASSERT_HTTP(tok.isTokenValid(auth, type::Scope::retrieveGenre), Status::CODE_403, "Not allowed");
|
||||
|
||||
database::GenreRepository gnrRepo(m_bConf);
|
||||
model::Genre gnrDb(id);
|
||||
|
||||
OATPP_ASSERT_HTTP(gnrRepo.doesGenreExist(gnrDb, type::GenreFilter::id) , Status::CODE_403, "genre does not exist");
|
||||
|
||||
std::cout << "genre exist" << std::endl;
|
||||
gnrDb = gnrRepo.retrieveRecord(gnrDb, type::GenreFilter::id);
|
||||
|
||||
auto genre = dto::GenreDto::createShared();
|
||||
genre->id = gnrDb.id;
|
||||
genre->category= gnrDb.category.c_str();
|
||||
|
||||
return createDtoResponse(Status::CODE_200, genre);
|
||||
}
|
||||
|
||||
#include OATPP_CODEGEN_END(ApiController)
|
||||
private:
|
||||
std::string m_exe_path;
|
||||
model::BinaryPath m_bConf;
|
||||
};
|
||||
}
|
||||
#endif
|
||||
@@ -1,61 +0,0 @@
|
||||
#ifndef LOGINCONTROLLER_H_
|
||||
#define LOGINCONTROLLER_H_
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
#include "oatpp/core/macro/codegen.hpp"
|
||||
#include "oatpp/core/macro/component.hpp"
|
||||
#include "oatpp/web/server/api/ApiController.hpp"
|
||||
|
||||
#include "dto/LoginResultDto.hpp"
|
||||
#include "dto/conversion/DtoConversions.h"
|
||||
#include "manager/TokenManager.h"
|
||||
#include "manager/UserManager.h"
|
||||
#include "model/Models.h"
|
||||
|
||||
namespace controller {
|
||||
class LoginController : public oatpp::web::server::api::ApiController {
|
||||
public:
|
||||
LoginController(std::string p, OATPP_COMPONENT(std::shared_ptr<ObjectMapper>, objectMapper))
|
||||
: oatpp::web::server::api::ApiController(objectMapper), exe_path(p)
|
||||
{ }
|
||||
LoginController(const model::BinaryPath& bConf, OATPP_COMPONENT(std::shared_ptr<ObjectMapper>, objectMapper))
|
||||
:oatpp::web::server::api::ApiController(objectMapper), m_bConf(bConf)
|
||||
{ }
|
||||
|
||||
|
||||
#include OATPP_CODEGEN_BEGIN(ApiController)
|
||||
|
||||
ENDPOINT("POST", "/api/v1/login", data, BODY_DTO(dto::UserDto::ObjectWrapper, usr)) {
|
||||
OATPP_LOGI("icarus", "logging in");
|
||||
|
||||
manager::UserManager usrMgr(m_bConf);
|
||||
auto user = dto::conversion::DtoConversions::toUser(usr);
|
||||
|
||||
if (!usrMgr.doesUserExist(user) || !usrMgr.validatePassword(user)) {
|
||||
auto logRes = dto::LoginResultDto::createShared();
|
||||
logRes->message = "invalid credentials";
|
||||
std::cout << "user does not exist" << std::endl;
|
||||
return createDtoResponse(Status::CODE_401, logRes);
|
||||
}
|
||||
|
||||
std::cout << "user exists" << std::endl;
|
||||
|
||||
manager::TokenManager tok;
|
||||
auto token = tok.retrieveToken(m_bConf);
|
||||
|
||||
auto logRes = dto::conversion::DtoConversions::toLoginResultDto(user, token);
|
||||
logRes->message = "Successful";
|
||||
|
||||
return createDtoResponse(Status::CODE_200, logRes);
|
||||
}
|
||||
|
||||
#include OATPP_CODEGEN_END(ApiController)
|
||||
private:
|
||||
std::string exe_path;
|
||||
model::BinaryPath m_bConf;
|
||||
};
|
||||
}
|
||||
#endif
|
||||
@@ -1,41 +0,0 @@
|
||||
#ifndef REGISTERCONTROLLER_H_
|
||||
#define REGISTERCONTROLLER_H_
|
||||
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
|
||||
#include "oatpp/core/macro/codegen.hpp"
|
||||
#include "oatpp/core/macro/component.hpp"
|
||||
#include "oatpp/web/server/api/ApiController.hpp"
|
||||
|
||||
#include "dto/LoginResultDto.hpp"
|
||||
#include "dto/conversion/DtoConversions.h"
|
||||
#include "manager/UserManager.h"
|
||||
#include "model/Models.h"
|
||||
|
||||
namespace controller {
|
||||
class RegisterController : public oatpp::web::server::api::ApiController {
|
||||
public:
|
||||
RegisterController(const model::BinaryPath& bConf,
|
||||
OATPP_COMPONENT(std::shared_ptr<ObjectMapper>, objectMapper)) :
|
||||
oatpp::web::server::api::ApiController(objectMapper), m_bConf(bConf) { }
|
||||
|
||||
#include OATPP_CODEGEN_BEGIN(ApiController)
|
||||
|
||||
ENDPOINT("POST", "api/v1/register", registerUser,
|
||||
BODY_DTO(dto::UserDto::ObjectWrapper, usr)) {
|
||||
manager::UserManager usrMgr(m_bConf);
|
||||
auto user = dto::conversion::DtoConversions::toUser(usr);
|
||||
|
||||
auto res = usrMgr.registerUser(user);
|
||||
auto registerResult = dto::conversion::DtoConversions::toRegisterResultDto(res);
|
||||
|
||||
return createDtoResponse(Status::CODE_200, registerResult);
|
||||
}
|
||||
#include OATPP_CODEGEN_END(ApiController)
|
||||
private:
|
||||
model::BinaryPath m_bConf;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,240 +0,0 @@
|
||||
#ifndef SONGCONTROLLER_H_
|
||||
#define SONGCONTROLLER_H_
|
||||
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "oatpp/core/data/stream/ChunkedBuffer.hpp"
|
||||
#include "oatpp/core/data/stream/FileStream.hpp"
|
||||
#include "oatpp/core/macro/codegen.hpp"
|
||||
#include "oatpp/core/macro/component.hpp"
|
||||
#include "oatpp/web/mime/multipart/InMemoryPartReader.hpp"
|
||||
#include "oatpp/web/mime/multipart/Reader.hpp"
|
||||
#include "oatpp/web/protocol/http/outgoing/ChunkedBody.hpp"
|
||||
#include "oatpp/web/server/api/ApiController.hpp"
|
||||
|
||||
#include "callback/StreamCallback.h"
|
||||
#include "database/SongRepository.h"
|
||||
#include "dto/SongDto.hpp"
|
||||
#include "dto/conversion/DtoConversions.h"
|
||||
#include "manager/SongManager.h"
|
||||
#include "manager/TokenManager.h"
|
||||
#include "model/Models.h"
|
||||
#include "type/Scopes.h"
|
||||
#include "type/SongFilter.h"
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace controller {
|
||||
class SongController : public oatpp::web::server::api::ApiController {
|
||||
public:
|
||||
SongController(std::string p, OATPP_COMPONENT(std::shared_ptr<ObjectMapper>, objectMapper))
|
||||
: oatpp::web::server::api::ApiController(objectMapper), m_exe_path(p)
|
||||
{ }
|
||||
|
||||
SongController(const model::BinaryPath& bConf, OATPP_COMPONENT(std::shared_ptr<ObjectMapper>, objectMapper))
|
||||
: oatpp::web::server::api::ApiController(objectMapper), m_bConf(bConf)
|
||||
{ }
|
||||
|
||||
#include OATPP_CODEGEN_BEGIN(ApiController)
|
||||
|
||||
// endpoint for uploading a song
|
||||
ENDPOINT("POST", "/api/v1/song/data", songUpload,
|
||||
REQUEST(std::shared_ptr<IncomingRequest>, request))
|
||||
{
|
||||
auto authHeader = request->getHeader("Authorization");
|
||||
OATPP_ASSERT_HTTP(authHeader, Status::CODE_403, "Nope");
|
||||
auto auth = authHeader->std_str();
|
||||
manager::TokenManager tok;
|
||||
OATPP_ASSERT_HTTP(tok.isTokenValid(auth, type::Scope::upload), Status::CODE_403, "Not allowed");
|
||||
|
||||
auto mp = std::make_shared<oatpp::web::mime::multipart::Multipart>(request->getHeaders());
|
||||
|
||||
oatpp::web::mime::multipart::Reader mp_reader(mp.get());
|
||||
|
||||
mp_reader.setPartReader("file", oatpp::web::mime::multipart::createInMemoryPartReader(m_dataSize));
|
||||
|
||||
request->transferBody(&mp_reader);
|
||||
|
||||
auto file = mp->getNamedPart("file");
|
||||
|
||||
OATPP_ASSERT_HTTP(file, Status::CODE_400, "file is null");
|
||||
|
||||
auto buff = std::unique_ptr<char>(new char[file->getKnownSize()]);
|
||||
auto buffSize = file->getInputStream()->read(buff.get(), file->getKnownSize());
|
||||
|
||||
std::vector<unsigned char> data(buff.get(), buff.get() + buffSize);
|
||||
|
||||
model::Song sng;
|
||||
sng.data = std::move(data);
|
||||
|
||||
manager::SongManager songMgr(m_bConf);
|
||||
songMgr.saveSong(sng);
|
||||
auto songDto = dto::conversion::DtoConversions::toSongDto(sng);
|
||||
|
||||
return createDtoResponse(Status::CODE_200, songDto);
|
||||
}
|
||||
|
||||
// endpoint for retrieving all song records in json format
|
||||
ENDPOINT("GET", "/api/v1/song", songRecords,
|
||||
REQUEST(std::shared_ptr<IncomingRequest>, request))
|
||||
{
|
||||
auto authHeader = request->getHeader("Authorization");
|
||||
OATPP_ASSERT_HTTP(authHeader, Status::CODE_403, "Nope");
|
||||
auto auth = authHeader->std_str();
|
||||
manager::TokenManager tok;
|
||||
OATPP_ASSERT_HTTP(tok.isTokenValid(auth, type::Scope::retrieveSong), Status::CODE_403, "Not allowed");
|
||||
|
||||
std::cout << "starting process of retrieving songs" << std::endl;
|
||||
database::SongRepository songRepo(m_bConf);
|
||||
auto songsDb = songRepo.retrieveRecords();
|
||||
auto songs = oatpp::data::mapping::type::List<dto::SongDto::ObjectWrapper>::createShared();
|
||||
|
||||
std::cout << "creating object to send" << std::endl;
|
||||
for (auto& songDb : songsDb) {
|
||||
auto song = dto::conversion::DtoConversions::toSongDto(songDb);
|
||||
|
||||
songs->pushBack(song);
|
||||
}
|
||||
|
||||
return createDtoResponse(Status::CODE_200, songs);
|
||||
}
|
||||
|
||||
// endpoint for retrieving song record by the song id in json format
|
||||
ENDPOINT("GET", "/api/v1/song/{id}", songRecord,
|
||||
REQUEST(std::shared_ptr<IncomingRequest>, request), PATH(Int32, id)) {
|
||||
auto authHeader = request->getHeader("Authorization");
|
||||
OATPP_ASSERT_HTTP(authHeader, Status::CODE_403, "Nope");
|
||||
auto auth = authHeader->std_str();
|
||||
manager::TokenManager tok;
|
||||
OATPP_ASSERT_HTTP(tok.isTokenValid(auth, type::Scope::retrieveSong), Status::CODE_403, "Not allowed");
|
||||
|
||||
database::SongRepository songRepo(m_bConf);
|
||||
model::Song songDb(id);
|
||||
|
||||
if (!songRepo.doesSongExist(songDb, type::SongFilter::id)) {
|
||||
return songDoesNotExist();
|
||||
}
|
||||
|
||||
std::cout << "song exists" << std::endl;
|
||||
songDb = songRepo.retrieveRecord(songDb, type::SongFilter::id);
|
||||
|
||||
auto song = dto::conversion::DtoConversions::toSongDto(songDb);
|
||||
|
||||
return createDtoResponse(Status::CODE_200, song);
|
||||
}
|
||||
|
||||
ENDPOINT("GET", "/api/v1/song/data/{id}", downloadSong,
|
||||
REQUEST(std::shared_ptr<IncomingRequest>, request), PATH(Int32, id)) {
|
||||
auto authHeader = request->getHeader("Authorization");
|
||||
OATPP_ASSERT_HTTP(authHeader, Status::CODE_403, "Nope");
|
||||
auto auth = authHeader->std_str();
|
||||
manager::TokenManager tok;
|
||||
OATPP_ASSERT_HTTP(tok.isTokenValid(auth, type::Scope::download), Status::CODE_403, "Not allowed");
|
||||
|
||||
database::SongRepository songRepo(m_bConf);
|
||||
model::Song songDb(id);
|
||||
if (!songRepo.doesSongExist(songDb, type::SongFilter::id)) {
|
||||
return songDoesNotExist();
|
||||
}
|
||||
|
||||
songDb = songRepo.retrieveRecord(songDb, type::SongFilter::id);
|
||||
|
||||
auto rawSong = oatpp::base::StrBuffer::loadFromFile(songDb.songPath.c_str());
|
||||
|
||||
auto response = createResponse(Status::CODE_200, rawSong);
|
||||
response->putHeader(Header::CONTENT_TYPE, "audio/mpeg");
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
ENDPOINT("UPDATE", "/api/v1/song/{id}", songUpdate,
|
||||
REQUEST(std::shared_ptr<IncomingRequest>, request),
|
||||
BODY_DTO(dto::SongDto::ObjectWrapper, songDto), PATH(Int32, id)) {
|
||||
songDto->id = id;
|
||||
auto authHeader = request->getHeader("Authorization");
|
||||
OATPP_ASSERT_HTTP(authHeader, Status::CODE_403, "Nope");
|
||||
auto auth = authHeader->std_str();
|
||||
manager::TokenManager tok;
|
||||
OATPP_ASSERT_HTTP(tok.isTokenValid(auth, type::Scope::updateSong), Status::CODE_403, "Not allowed");
|
||||
|
||||
auto updatedSong = dto::conversion::DtoConversions::toSong(songDto);
|
||||
manager::SongManager songMgr(m_bConf);
|
||||
auto result = songMgr.updateSong(updatedSong);
|
||||
if (!result) {
|
||||
return songDoesNotExist();
|
||||
}
|
||||
|
||||
return createResponse(Status::CODE_200, "OK");
|
||||
}
|
||||
|
||||
// endpoint to delete a song
|
||||
ENDPOINT("DELETE", "api/v1/song/data/{id}", songDelete,
|
||||
REQUEST(std::shared_ptr<IncomingRequest>, request), PATH(Int32, id)) {
|
||||
auto authHeader = request->getHeader("Authorization");
|
||||
OATPP_ASSERT_HTTP(authHeader, Status::CODE_403, "Nope");
|
||||
auto auth = authHeader->std_str();
|
||||
manager::TokenManager tok;
|
||||
OATPP_ASSERT_HTTP(tok.isTokenValid(auth, type::Scope::deleteSong), Status::CODE_403, "Not allowed");
|
||||
|
||||
model::Song song(id);
|
||||
|
||||
manager::SongManager sngMgr(m_bConf);
|
||||
auto result = sngMgr.deleteSong(song);
|
||||
if (!result) {
|
||||
return songDoesNotExist();
|
||||
}
|
||||
|
||||
return createResponse(Status::CODE_200, "OK");
|
||||
}
|
||||
|
||||
// endpoint for streaming a song
|
||||
ENDPOINT("GET", "/api/v1/song/stream/{id}", streamSong,
|
||||
REQUEST(std::shared_ptr<IncomingRequest>, request), PATH(Int32, id)) {
|
||||
auto authHeader = request->getHeader("Authorization");
|
||||
OATPP_ASSERT_HTTP(authHeader, Status::CODE_403, "Nope");
|
||||
auto auth = authHeader->std_str();
|
||||
manager::TokenManager tok;
|
||||
OATPP_ASSERT_HTTP(tok.isTokenValid(auth, type::Scope::stream), Status::CODE_403, "Not allowed");
|
||||
|
||||
database::SongRepository songRepo(m_bConf);
|
||||
model::Song songDb(id);
|
||||
if (!songRepo.doesSongExist(songDb, type::SongFilter::id)) {
|
||||
return songDoesNotExist();
|
||||
}
|
||||
|
||||
songDb = songRepo.retrieveRecord(songDb, type::SongFilter::id);
|
||||
|
||||
oatpp::data::v_io_size dSize = 1024;
|
||||
|
||||
auto db = std::make_shared<oatpp::web::protocol::http::outgoing::ChunkedBody>(
|
||||
std::make_shared<callback::StreamCallback>(songDb.songPath), nullptr, dSize);
|
||||
|
||||
auto response = OutgoingResponse::createShared(Status::CODE_200, db);
|
||||
response->putHeader(Header::CONNECTION, Header::Value::CONNECTION_KEEP_ALIVE);
|
||||
response->putHeader(Header::CONTENT_TYPE, "audio/mpeg");
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
#include OATPP_CODEGEN_END(ApiController)
|
||||
private:
|
||||
std::shared_ptr<oatpp::web::protocol::http::outgoing::Response>
|
||||
songDoesNotExist() {
|
||||
|
||||
return createResponse(Status::CODE_404, "Song not found");
|
||||
}
|
||||
|
||||
std::string m_exe_path;
|
||||
|
||||
model::BinaryPath m_bConf;
|
||||
|
||||
const long m_dataSize = std::numeric_limits<long long int>::max();
|
||||
};
|
||||
}
|
||||
#endif
|
||||
@@ -1,99 +0,0 @@
|
||||
#ifndef YEARCONTROLLER_H_
|
||||
#define YEARCONTROLLER_H_
|
||||
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "oatpp/core/data/stream/ChunkedBuffer.hpp"
|
||||
#include "oatpp/core/data/stream/FileStream.hpp"
|
||||
#include "oatpp/core/macro/codegen.hpp"
|
||||
#include "oatpp/core/macro/component.hpp"
|
||||
#include "oatpp/web/mime/multipart/InMemoryPartReader.hpp"
|
||||
#include "oatpp/web/mime/multipart/Reader.hpp"
|
||||
#include "oatpp/web/server/api/ApiController.hpp"
|
||||
|
||||
#include "database/YearRepository.h"
|
||||
#include "dto/YearDto.hpp"
|
||||
#include "manager/YearManager.h"
|
||||
#include "model/Models.h"
|
||||
#include "type/Scopes.h"
|
||||
#include "type/YearFilter.h"
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace controller {
|
||||
class YearController : public oatpp::web::server::api::ApiController
|
||||
{
|
||||
public:
|
||||
YearController(std::string p, OATPP_COMPONENT(std::shared_ptr<ObjectMapper>, objectMapper))
|
||||
: oatpp::web::server::api::ApiController(objectMapper), m_exe_path(p)
|
||||
{ }
|
||||
|
||||
YearController(const model::BinaryPath& bConf, OATPP_COMPONENT(std::shared_ptr<ObjectMapper>, objectMapper))
|
||||
: oatpp::web::server::api::ApiController(objectMapper), m_bConf(bConf)
|
||||
{ }
|
||||
|
||||
#include OATPP_CODEGEN_BEGIN(ApiController)
|
||||
|
||||
// endpoint for retrieving all year records in json format
|
||||
ENDPOINT("GET", "/api/v1/year", yearRecords,
|
||||
REQUEST(std::shared_ptr<IncomingRequest>, request))
|
||||
{
|
||||
auto authHeader = request->getHeader("Authorization");
|
||||
OATPP_ASSERT_HTTP(authHeader, Status::CODE_403, "Nope");
|
||||
auto auth = authHeader->std_str();
|
||||
manager::TokenManager tok;
|
||||
OATPP_ASSERT_HTTP(tok.isTokenValid(auth, type::Scope::retrieveYear), Status::CODE_403, "Not allowed");
|
||||
|
||||
std::cout << "starting process of retrieving year" << std::endl;
|
||||
database::YearRepository yrRepo(m_bConf);
|
||||
auto yrsDb = yrRepo.retrieveRecords();
|
||||
auto yearRecs = oatpp::data::mapping::type::List<dto::YearDto::ObjectWrapper>::createShared();
|
||||
|
||||
for (auto& yrDb : yrsDb) {
|
||||
auto yr = dto::YearDto::createShared();
|
||||
yr->id = yrDb.id;
|
||||
yr->year = yrDb.year;
|
||||
|
||||
yearRecs->pushBack(yr);
|
||||
}
|
||||
|
||||
return createDtoResponse(Status::CODE_200, yearRecs);
|
||||
}
|
||||
|
||||
// endpoint for retrieving single year record by the year id in json format
|
||||
ENDPOINT("GET", "/api/v1/year/{id}", yearRecord,
|
||||
REQUEST(std::shared_ptr<IncomingRequest>, request), PATH(Int32, id)) {
|
||||
auto authHeader = request->getHeader("Authorization");
|
||||
OATPP_ASSERT_HTTP(authHeader, Status::CODE_403, "Nope");
|
||||
auto auth = authHeader->std_str();
|
||||
manager::TokenManager tok;
|
||||
OATPP_ASSERT_HTTP(tok.isTokenValid(auth, type::Scope::retrieveYear), Status::CODE_403, "Not allowed");
|
||||
|
||||
database::YearRepository yrRepo(m_bConf);
|
||||
model::Year yrDb(id);
|
||||
|
||||
OATPP_ASSERT_HTTP(yrRepo.doesYearExist(yrDb, type::YearFilter::id) , Status::CODE_403, "year does not exist");
|
||||
|
||||
std::cout << "year exist" << std::endl;
|
||||
yrDb = yrRepo.retrieveRecord(yrDb, type::YearFilter::id);
|
||||
|
||||
auto year = dto::YearDto::createShared();
|
||||
year->id = yrDb.id;
|
||||
year->year= yrDb.year;
|
||||
|
||||
return createDtoResponse(Status::CODE_200, year);
|
||||
}
|
||||
|
||||
#include OATPP_CODEGEN_END(ApiController)
|
||||
private:
|
||||
std::string m_exe_path;
|
||||
model::BinaryPath m_bConf;
|
||||
};
|
||||
}
|
||||
#endif
|
||||
@@ -1,40 +0,0 @@
|
||||
#ifndef ALBUMREPOSITORY_H_
|
||||
#define ALBUMREPOSITORY_H_
|
||||
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "database/BaseRepository.h"
|
||||
#include "model/Models.h"
|
||||
#include "type/AlbumFilter.h"
|
||||
|
||||
namespace database
|
||||
{
|
||||
class AlbumRepository : public BaseRepository
|
||||
{
|
||||
public:
|
||||
AlbumRepository(const model::BinaryPath&);
|
||||
|
||||
std::vector<model::Album> retrieveRecords();
|
||||
|
||||
std::pair<model::Album, int> retrieveRecordWithSongCount(model::Album&, type::AlbumFilter);
|
||||
|
||||
model::Album retrieveRecord(model::Album&, type::AlbumFilter);
|
||||
|
||||
bool doesAlbumExists(const model::Album&, type::AlbumFilter);
|
||||
|
||||
void saveAlbum(const model::Album&);
|
||||
void deleteAlbum(const model::Album&, type::AlbumFilter);
|
||||
private:
|
||||
std::vector<model::Album> parseRecords(MYSQL_STMT*);
|
||||
|
||||
std::pair<model::Album, int> parseRecordWithSongCount(MYSQL_STMT*);
|
||||
|
||||
// TODO: after parseRecord(MYSQL_STMT*) is implemented remove
|
||||
// parseRecord(MYSQL_RES*)
|
||||
model::Album parseRecord(MYSQL_RES*);
|
||||
model::Album parseRecord(MYSQL_STMT*);
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,40 +0,0 @@
|
||||
#ifndef ARTISTREPOSITORY_H_
|
||||
#define ARTISTREPOSITORY_H_
|
||||
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "database/BaseRepository.h"
|
||||
#include "model/Models.h"
|
||||
#include "type/ArtistFilter.h"
|
||||
|
||||
namespace database
|
||||
{
|
||||
class ArtistRepository : public BaseRepository
|
||||
{
|
||||
public:
|
||||
ArtistRepository(const model::BinaryPath&);
|
||||
|
||||
std::vector<model::Artist> retrieveRecords();
|
||||
|
||||
std::pair<model::Artist, int> retrieveRecordWithSongCount(model::Artist&, type::ArtistFilter);
|
||||
|
||||
model::Artist retrieveRecord(model::Artist&, type::ArtistFilter);
|
||||
|
||||
bool doesArtistExist(const model::Artist&, type::ArtistFilter);
|
||||
|
||||
void saveRecord(const model::Artist&);
|
||||
void deleteArtist(const model::Artist&, type::ArtistFilter);
|
||||
private:
|
||||
std::vector<model::Artist> parseRecords(MYSQL_STMT*);
|
||||
|
||||
std::pair<model::Artist, int> parseRecordWithSongCount(MYSQL_STMT*);
|
||||
|
||||
// TODO: After parseRecord(MYSQL_STMT*) is implemented
|
||||
// remove parseRecord(MYSQL_RES*)
|
||||
model::Artist parseRecord(MYSQL_RES*);
|
||||
model::Artist parseRecord(MYSQL_STMT*);
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,36 +0,0 @@
|
||||
#ifndef BASE_REPOSITORY_H_
|
||||
#define BASE_REPOSITORY_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <mysql/mysql.h>
|
||||
|
||||
#include "model/Models.h"
|
||||
|
||||
namespace database
|
||||
{
|
||||
class BaseRepository
|
||||
{
|
||||
public:
|
||||
BaseRepository();
|
||||
BaseRepository(const std::string&);
|
||||
BaseRepository(const model::BinaryPath&);
|
||||
|
||||
bool testConnection();
|
||||
protected:
|
||||
MYSQL* setupMysqlConnection();
|
||||
MYSQL* setupMysqlConnection(model::DatabaseConnection);
|
||||
|
||||
MYSQL_RES* performMysqlQuery(MYSQL*, const std::string&);
|
||||
|
||||
model::DatabaseConnection details;
|
||||
private:
|
||||
void intitalizeDetails();
|
||||
void initializeDetails(const model::BinaryPath&);
|
||||
|
||||
std::string path;
|
||||
model::BinaryPath m_binConf;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,39 +0,0 @@
|
||||
#ifndef COVERARTREPOSITORY_H_
|
||||
#define COVERARTREPOSITORY_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <mysql/mysql.h>
|
||||
|
||||
#include "database/BaseRepository.h"
|
||||
#include "model/Models.h"
|
||||
#include "type/CoverFilter.h"
|
||||
|
||||
namespace database
|
||||
{
|
||||
class CoverArtRepository : public BaseRepository
|
||||
{
|
||||
public:
|
||||
CoverArtRepository(const std::string&);
|
||||
CoverArtRepository(const model::BinaryPath&);
|
||||
|
||||
std::vector<model::Cover> retrieveRecords();
|
||||
|
||||
model::Cover retrieveRecord(model::Cover&, type::CoverFilter);
|
||||
|
||||
bool doesCoverArtExist(const model::Cover&, type::CoverFilter);
|
||||
|
||||
void deleteRecord(const model::Cover&);
|
||||
void saveRecord(const model::Cover&);
|
||||
void updateRecord(const model::Cover&);
|
||||
private:
|
||||
std::vector<model::Cover> parseRecords(MYSQL_STMT*);
|
||||
|
||||
// TODO: After parseRecord(MYSQL_STMT*) is implemented
|
||||
// remove parseRecord(MYSQL_RES*)
|
||||
model::Cover parseRecord(MYSQL_RES*);
|
||||
model::Cover parseRecord(MYSQL_STMT*);
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,40 +0,0 @@
|
||||
#ifndef GENREREPOSITORY_H_
|
||||
#define GENREREPOSITORY_H_
|
||||
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "database/BaseRepository.h"
|
||||
#include "model/Models.h"
|
||||
#include "type/GenreFilter.h"
|
||||
|
||||
namespace database
|
||||
{
|
||||
class GenreRepository : public BaseRepository
|
||||
{
|
||||
public:
|
||||
GenreRepository(const model::BinaryPath&);
|
||||
|
||||
std::vector<model::Genre> retrieveRecords();
|
||||
|
||||
std::pair<model::Genre, int> retrieveRecordWithSongCount(model::Genre&, type::GenreFilter);
|
||||
|
||||
model::Genre retrieveRecord(model::Genre&, type::GenreFilter);
|
||||
|
||||
bool doesGenreExist(const model::Genre&, type::GenreFilter);
|
||||
|
||||
void saveRecord(const model::Genre&);
|
||||
void deleteRecord(const model::Genre&, type::GenreFilter);
|
||||
private:
|
||||
std::vector<model::Genre> parseRecords(MYSQL_STMT*);
|
||||
|
||||
std::pair<model::Genre, int> parseRecordWithSongCount(MYSQL_STMT*);
|
||||
|
||||
// TODO: After parseRecord(MYSQL_STMT*) is implemented
|
||||
// remove parseRecord(MYSQL_RES*)
|
||||
model::Genre parseRecord(MYSQL_RES*);
|
||||
model::Genre parseRecord(MYSQL_STMT*);
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,39 +0,0 @@
|
||||
#ifndef SONGREPOSITORY_H_
|
||||
#define SONGREPOSITORY_H_
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include <mysql/mysql.h>
|
||||
|
||||
#include "database/BaseRepository.h"
|
||||
#include "model/Models.h"
|
||||
#include "type/SongFilter.h"
|
||||
|
||||
namespace database
|
||||
{
|
||||
class SongRepository : public BaseRepository
|
||||
{
|
||||
public:
|
||||
SongRepository(const std::string&);
|
||||
SongRepository(const model::BinaryPath&);
|
||||
|
||||
std::vector<model::Song> retrieveRecords();
|
||||
|
||||
model::Song retrieveRecord(model::Song&, type::SongFilter);
|
||||
|
||||
bool doesSongExist(const model::Song&, type::SongFilter);
|
||||
bool deleteRecord(const model::Song&);
|
||||
|
||||
void saveRecord(const model::Song&);
|
||||
void updateRecord(const model::Song&);
|
||||
private:
|
||||
std::vector<model::Song> parseRecords(MYSQL_RES*); // TODO: to be removed
|
||||
std::vector<model::Song> parseRecords(MYSQL_STMT*);
|
||||
|
||||
model::Song parseRecord(MYSQL_RES*); // TODO: to be removed
|
||||
model::Song parseRecord(MYSQL_STMT*);
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,58 +0,0 @@
|
||||
#ifndef USERREPOSITORY_H_
|
||||
#define USERREPOSITORY_H_
|
||||
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <tuple>
|
||||
|
||||
#include "database/BaseRepository.h"
|
||||
#include "model/Models.h"
|
||||
#include "type/SaltFilter.h"
|
||||
#include "type/UserFilter.h"
|
||||
|
||||
namespace database {
|
||||
class UserRepository : BaseRepository {
|
||||
public:
|
||||
UserRepository(const model::BinaryPath&);
|
||||
|
||||
model::User retrieveUserRecord(model::User&, type::UserFilter);
|
||||
model::PassSec retrieverUserSaltRecord(model::PassSec&, type::SaltFilter);
|
||||
|
||||
bool doesUserRecordExist(const model::User&, type::UserFilter);
|
||||
|
||||
void saveUserRecord(const model::User&);
|
||||
void saveUserSalt(const model::PassSec&);
|
||||
private:
|
||||
struct UserLengths;
|
||||
struct SaltLengths;
|
||||
|
||||
struct UserLengths
|
||||
{
|
||||
unsigned long firstnameLength;
|
||||
unsigned long lastnameLength;
|
||||
unsigned long phoneLength;
|
||||
unsigned long emailLength;
|
||||
unsigned long usernameLength;
|
||||
unsigned long passwordLength;
|
||||
};
|
||||
struct SaltLengths
|
||||
{
|
||||
unsigned long saltLength;
|
||||
};
|
||||
|
||||
std::shared_ptr<MYSQL_BIND> insertUserValues(const model::User&, std::shared_ptr<UserLengths>);
|
||||
std::shared_ptr<MYSQL_BIND> insertSaltValues(const model::PassSec&, std::shared_ptr<SaltLengths>);
|
||||
std::shared_ptr<MYSQL_BIND> valueBind(model::User&, std::tuple<char*, char*, char*, char*, char*, char*>&);
|
||||
std::shared_ptr<MYSQL_BIND> saltValueBind(model::PassSec&, char*);
|
||||
std::shared_ptr<UserLengths> fetchUserLengths(const model::User&);
|
||||
std::shared_ptr<SaltLengths> fetchSaltLengths(const model::PassSec&);
|
||||
|
||||
std::tuple<char*, char*, char*, char*, char*, char*> fetchUV();
|
||||
|
||||
model::User parseRecord(MYSQL_STMT*);
|
||||
model::PassSec parseSaltRecord(MYSQL_STMT*);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
@@ -1,40 +0,0 @@
|
||||
#ifndef YEARREPOSITORY_H_
|
||||
#define YEARREPOSITORY_H_
|
||||
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "database/BaseRepository.h"
|
||||
#include "model/Models.h"
|
||||
#include "type/YearFilter.h"
|
||||
|
||||
namespace database
|
||||
{
|
||||
class YearRepository : public BaseRepository
|
||||
{
|
||||
public:
|
||||
YearRepository(const model::BinaryPath&);
|
||||
|
||||
std::vector<model::Year> retrieveRecords();
|
||||
|
||||
std::pair<model::Year, int> retrieveRecordWithSongCount(model::Year&, type::YearFilter);
|
||||
|
||||
model::Year retrieveRecord(model::Year&, type::YearFilter);
|
||||
|
||||
bool doesYearExist(const model::Year&, type::YearFilter);
|
||||
|
||||
void saveRecord(const model::Year&);
|
||||
void deleteYear(const model::Year&, type::YearFilter);
|
||||
private:
|
||||
std::vector<model::Year> parseRecords(MYSQL_STMT*);
|
||||
|
||||
std::pair<model::Year, int> parseRecordWithSongCount(MYSQL_STMT*);
|
||||
|
||||
// TODO: After parseRecord(MYSQL_STMT*) is implemented
|
||||
// remove parseRecord(MYSQL_RES*)
|
||||
model::Year parseRecord(MYSQL_RES*);
|
||||
model::Year parseRecord(MYSQL_STMT*);
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,23 +0,0 @@
|
||||
#ifndef ALBUMDTO_H_
|
||||
#define ALBUMDTO_H_
|
||||
|
||||
#include "oatpp/core/data/mapping/type/Object.hpp"
|
||||
#include "oatpp/core/macro/codegen.hpp"
|
||||
|
||||
namespace dto
|
||||
{
|
||||
#include OATPP_CODEGEN_BEGIN(DTO)
|
||||
|
||||
class AlbumDto : public oatpp::data::mapping::type::Object
|
||||
{
|
||||
DTO_INIT(AlbumDto, Object)
|
||||
|
||||
DTO_FIELD(Int32, id);
|
||||
DTO_FIELD(String, title);
|
||||
DTO_FIELD(Int32, year);
|
||||
};
|
||||
|
||||
#include OATPP_CODEGEN_END(DTO)
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,22 +0,0 @@
|
||||
#ifndef ARTISTDTO_H_
|
||||
#define ARTISTDTO_H_
|
||||
|
||||
#include "oatpp/core/data/mapping/type/Object.hpp"
|
||||
#include "oatpp/core/macro/codegen.hpp"
|
||||
|
||||
namespace dto
|
||||
{
|
||||
#include OATPP_CODEGEN_BEGIN(DTO)
|
||||
|
||||
class ArtistDto : public oatpp::data::mapping::type::Object
|
||||
{
|
||||
DTO_INIT(ArtistDto, Object)
|
||||
|
||||
DTO_FIELD(Int32, id);
|
||||
DTO_FIELD(String, artist);
|
||||
};
|
||||
|
||||
#include OATPP_CODEGEN_END(DTO)
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,22 +0,0 @@
|
||||
#ifndef COVERARTDTO_H_
|
||||
#define COVERARTDTO_H_
|
||||
|
||||
#include "oatpp/core/data/mapping/type/Object.hpp"
|
||||
#include "oatpp/core/macro/codegen.hpp"
|
||||
|
||||
namespace dto
|
||||
{
|
||||
#include OATPP_CODEGEN_BEGIN(DTO)
|
||||
|
||||
class CoverArtDto : public oatpp::data::mapping::type::Object
|
||||
{
|
||||
DTO_INIT(CoverArtDto, Object)
|
||||
|
||||
DTO_FIELD(Int32, id);
|
||||
DTO_FIELD(String, songTitle);
|
||||
};
|
||||
|
||||
#include OATPP_CODEGEN_END(DTO)
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,22 +0,0 @@
|
||||
#ifndef GENREDTO_H_
|
||||
#define GENREDTO_H_
|
||||
|
||||
#include "oatpp/core/data/mapping/type/Object.hpp"
|
||||
#include "oatpp/core/macro/codegen.hpp"
|
||||
|
||||
namespace dto
|
||||
{
|
||||
#include OATPP_CODEGEN_BEGIN(DTO)
|
||||
|
||||
class GenreDto : public oatpp::data::mapping::type::Object
|
||||
{
|
||||
DTO_INIT(GenreDto, Object)
|
||||
|
||||
DTO_FIELD(Int32, id);
|
||||
DTO_FIELD(String, category);
|
||||
};
|
||||
|
||||
#include OATPP_CODEGEN_END(DTO)
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,47 +0,0 @@
|
||||
#ifndef LOGINRESULTDTO_H_
|
||||
#define LOGINRESULTDTO_H_
|
||||
|
||||
#include "oatpp/core/data/mapping/type/Object.hpp"
|
||||
#include "oatpp/core/macro/codegen.hpp"
|
||||
|
||||
#include "model/Models.h"
|
||||
|
||||
namespace dto {
|
||||
#include OATPP_CODEGEN_BEGIN(DTO)
|
||||
|
||||
class LoginResultDto : public oatpp::data::mapping::type::Object {
|
||||
DTO_INIT(LoginResultDto, Object)
|
||||
|
||||
DTO_FIELD(Int32, id);
|
||||
DTO_FIELD(String, username);
|
||||
DTO_FIELD(String, token);
|
||||
DTO_FIELD(String, token_type);
|
||||
DTO_FIELD(Int32, expiration);
|
||||
DTO_FIELD(String, message);
|
||||
};
|
||||
|
||||
class RegisterResultDto : public oatpp::data::mapping::type::Object {
|
||||
DTO_INIT(RegisterResultDto, Object)
|
||||
|
||||
DTO_FIELD(String, username);
|
||||
DTO_FIELD(Boolean, registered);
|
||||
DTO_FIELD(String, message);
|
||||
};
|
||||
|
||||
class UserDto : public oatpp::data::mapping::type::Object {
|
||||
DTO_INIT(UserDto, Object)
|
||||
|
||||
DTO_FIELD(Int32, userId);
|
||||
DTO_FIELD(String, firstname);
|
||||
DTO_FIELD(String, lastname);
|
||||
DTO_FIELD(String, phone);
|
||||
DTO_FIELD(String, email);
|
||||
DTO_FIELD(String, username);
|
||||
DTO_FIELD(String, password);
|
||||
};
|
||||
|
||||
#include OATPP_CODEGEN_END(DTO)
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,34 +0,0 @@
|
||||
#ifndef SONGDTO_H_
|
||||
#define SONGDTO_H_
|
||||
|
||||
#include "oatpp/core/data/mapping/type/Object.hpp"
|
||||
#include "oatpp/core/macro/codegen.hpp"
|
||||
|
||||
#include "model/Models.h"
|
||||
|
||||
namespace dto
|
||||
{
|
||||
#include OATPP_CODEGEN_BEGIN(DTO)
|
||||
|
||||
class SongDto : public oatpp::data::mapping::type::Object
|
||||
{
|
||||
DTO_INIT(SongDto, Object)
|
||||
|
||||
DTO_FIELD(Int32, id);
|
||||
DTO_FIELD(String, title);
|
||||
DTO_FIELD(String, artist);
|
||||
DTO_FIELD(String, album);
|
||||
DTO_FIELD(String, genre);
|
||||
DTO_FIELD(Int32, track);
|
||||
DTO_FIELD(Int32, disc);
|
||||
DTO_FIELD(Int32, year);
|
||||
DTO_FIELD(Int32, duration);
|
||||
DTO_FIELD(Int32, coverart_id);
|
||||
};
|
||||
|
||||
#include OATPP_CODEGEN_END(DTO)
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,22 +0,0 @@
|
||||
#ifndef YEARDTO_H_
|
||||
#define YEARDTO_H_
|
||||
|
||||
#include "oatpp/core/data/mapping/type/Object.hpp"
|
||||
#include "oatpp/core/macro/codegen.hpp"
|
||||
|
||||
namespace dto
|
||||
{
|
||||
#include OATPP_CODEGEN_BEGIN(DTO)
|
||||
|
||||
class YearDto : public oatpp::data::mapping::type::Object
|
||||
{
|
||||
DTO_INIT(YearDto, Object)
|
||||
|
||||
DTO_FIELD(Int32, id);
|
||||
DTO_FIELD(Int32, year);
|
||||
};
|
||||
|
||||
#include OATPP_CODEGEN_END(DTO)
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,24 +0,0 @@
|
||||
#ifndef DTOCONVERSIONS_H_
|
||||
#define DTOCONVERSIONS_H_
|
||||
|
||||
#include "dto/LoginResultDto.hpp"
|
||||
#include "dto/SongDto.hpp"
|
||||
#include "model/Models.h"
|
||||
|
||||
namespace dto { namespace conversion {
|
||||
class DtoConversions {
|
||||
public:
|
||||
static dto::LoginResultDto::ObjectWrapper toLoginResultDto(const model::User&, const model::Token&);
|
||||
|
||||
static dto::RegisterResultDto::ObjectWrapper toRegisterResultDto(
|
||||
const model::RegisterResult&);
|
||||
|
||||
static dto::SongDto::ObjectWrapper toSongDto(const model::Song&);
|
||||
|
||||
static model::Song toSong(dto::SongDto::ObjectWrapper&);
|
||||
|
||||
static model::User toUser(dto::UserDto::ObjectWrapper&);
|
||||
};
|
||||
}}
|
||||
|
||||
#endif
|
||||
@@ -1,25 +0,0 @@
|
||||
#ifndef ALBUMMANAGER_H_
|
||||
#define ALBUMMANAGER_H_
|
||||
|
||||
#include "model/Models.h"
|
||||
|
||||
namespace manager
|
||||
{
|
||||
class AlbumManager
|
||||
{
|
||||
public:
|
||||
AlbumManager(const model::BinaryPath&);
|
||||
|
||||
model::Album retrieveAlbum(model::Album&);
|
||||
model::Album saveAlbum(const model::Song&);
|
||||
|
||||
void deleteAlbum(const model::Song&);
|
||||
void updateAlbum(model::Song&, const model::Song&);
|
||||
|
||||
static void printAlbum(const model::Album&);
|
||||
private:
|
||||
model::BinaryPath m_bConf;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,25 +0,0 @@
|
||||
#ifndef ARTISTMANAGER_H_
|
||||
#define ARTISTMANAGER_H_
|
||||
|
||||
#include "model/Models.h"
|
||||
|
||||
namespace manager
|
||||
{
|
||||
class ArtistManager
|
||||
{
|
||||
public:
|
||||
ArtistManager(const model::BinaryPath&);
|
||||
|
||||
model::Artist retrieveArtist(model::Artist&);
|
||||
model::Artist saveArtist(const model::Song&);
|
||||
|
||||
void deleteArtist(const model::Song&);
|
||||
void updateArtist(model::Song&, const model::Song&);
|
||||
|
||||
static void printArtist(const model::Artist&);
|
||||
private:
|
||||
model::BinaryPath m_bConf;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,32 +0,0 @@
|
||||
#ifndef COVERARTMANAGER_H_
|
||||
#define COVERARTMANAGER_H_
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "model/Models.h"
|
||||
|
||||
namespace manager
|
||||
{
|
||||
class CoverArtManager
|
||||
{
|
||||
public:
|
||||
CoverArtManager(const std::string&);
|
||||
CoverArtManager(const model::BinaryPath& bConf);
|
||||
|
||||
model::Cover saveCover(const model::Song&);
|
||||
|
||||
std::pair<bool, std::string> defaultCover(const model::Cover&);
|
||||
|
||||
void deleteCover(const model::Song&);
|
||||
void updateCover(const model::Song&, const model::Song&);
|
||||
void updateCoverRecord(const model::Song&);
|
||||
private:
|
||||
std::string createImagePath(const model::Song&);
|
||||
|
||||
model::BinaryPath m_bConf;
|
||||
std::string path;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,37 +0,0 @@
|
||||
#ifndef DIRECTORY_MANAGER_H_
|
||||
#define DIRECTORY_MANAGER_H_
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include "model/Models.h"
|
||||
#include "type/PathType.h"
|
||||
|
||||
namespace manager
|
||||
{
|
||||
class DirectoryManager
|
||||
{
|
||||
public:
|
||||
static std::string createDirectoryProcess(model::Song, const std::string&);
|
||||
static std::string createDirectoryProcess(const model::Song&, const model::BinaryPath&, type::PathType);
|
||||
static std::string configPath(std::string_view);
|
||||
static std::string configPath(const model::BinaryPath&);
|
||||
static std::string contentOfPath(const std::string&);
|
||||
static std::string retrievePathType(type::PathType);
|
||||
|
||||
static nlohmann::json credentialConfigContent(const model::BinaryPath&);
|
||||
static nlohmann::json databaseConfigContent(const model::BinaryPath&);
|
||||
static nlohmann::json pathConfigContent(const model::BinaryPath&);
|
||||
|
||||
static void deleteDirectories(model::Song, const std::string&);
|
||||
|
||||
void deleteCoverArtFile(const std::string&, const std::string&);
|
||||
|
||||
private:
|
||||
void deleteSong(const model::Song);
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,25 +0,0 @@
|
||||
#ifndef GENREMANAGER_H_
|
||||
#define GENREMANAGER_H_
|
||||
|
||||
#include "model/Models.h"
|
||||
|
||||
namespace manager
|
||||
{
|
||||
class GenreManager
|
||||
{
|
||||
public:
|
||||
GenreManager(const model::BinaryPath&);
|
||||
|
||||
model::Genre retrieveGenre(model::Genre&);
|
||||
model::Genre saveGenre(const model::Song&);
|
||||
|
||||
void deleteGenre(const model::Song&);
|
||||
void updateGenre(model::Song&, const model::Song&);
|
||||
|
||||
static void printGenre(const model::Genre&);
|
||||
private:
|
||||
model::BinaryPath m_bConf;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,51 +0,0 @@
|
||||
#ifndef SONGMANAGER_H_
|
||||
#define SONGMANAGER_H_
|
||||
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include "dto/SongDto.hpp"
|
||||
#include "model/Models.h"
|
||||
#include "type/SongChanged.h"
|
||||
|
||||
namespace manager {
|
||||
class SongManager
|
||||
{
|
||||
public:
|
||||
SongManager(std::string&);
|
||||
SongManager(const model::BinaryPath&);
|
||||
|
||||
|
||||
bool didSongChange(const model::Song&, const model::Song&);
|
||||
bool requiresFilesystemChange(const model::Song&, const model::Song&);
|
||||
bool deleteSong(model::Song&);
|
||||
bool updateSong(model::Song&);
|
||||
|
||||
void saveSong(model::Song&);
|
||||
|
||||
static void printSong(const model::Song&);
|
||||
private:
|
||||
std::map<type::SongChanged, bool> changesInSong(const model::Song&, const model::Song&);
|
||||
|
||||
std::string createSongPath(const model::Song&);
|
||||
|
||||
void assignMiscId(model::Song&, const model::Song&);
|
||||
void assignMiscFields(std::map<type::SongChanged, bool>&, model::Song&,
|
||||
const model::Song&);
|
||||
void saveSongTemp(model::Song&);
|
||||
void saveMisc(model::Song&);
|
||||
void deleteMisc(const model::Song&);
|
||||
void deleteMiscExceptCoverArt(const model::Song&);
|
||||
void updateMisc(const std::map<type::SongChanged, bool>&,
|
||||
model::Song&, const model::Song&);
|
||||
void modifySongOnFilesystem(model::Song&, const model::Song&);
|
||||
|
||||
model::BinaryPath m_bConf;
|
||||
std::string exe_path;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,40 +0,0 @@
|
||||
#ifndef TOKEN_MANAGER_H_
|
||||
#define TOKEN_MANAGER_H_
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <cpr/cpr.h>
|
||||
#include <jwt-cpp/jwt.h>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include "model/Models.h"
|
||||
#include "type/Scopes.h"
|
||||
|
||||
namespace manager {
|
||||
class TokenManager {
|
||||
public:
|
||||
TokenManager();
|
||||
|
||||
model::Token retrieveToken(const model::BinaryPath&);
|
||||
|
||||
bool isTokenValid(std::string&, type::Scope);
|
||||
bool testAuth(const model::BinaryPath&);
|
||||
private:
|
||||
cpr::Response sendRequest(std::string_view, nlohmann::json&);
|
||||
|
||||
nlohmann::json createTokenBody(const model::AuthCredentials&);
|
||||
|
||||
model::AuthCredentials parseAuthCredentials(std::string_view);
|
||||
model::AuthCredentials parseAuthCredentials(const model::BinaryPath&);
|
||||
|
||||
std::vector<std::string> extractScopes(const jwt::decoded_jwt&&);
|
||||
std::pair<bool, std::vector<std::string>> fetchAuthHeader(const std::string&);
|
||||
|
||||
bool tokenSupportsScope(const std::vector<std::string>&, const std::string&&);
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,25 +0,0 @@
|
||||
#ifndef USERMANAGER_H_
|
||||
#define USERMANAGER_H_
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "dto/LoginResultDto.hpp"
|
||||
#include "model/Models.h"
|
||||
|
||||
namespace manager {
|
||||
class UserManager {
|
||||
public:
|
||||
UserManager(const model::BinaryPath&);
|
||||
|
||||
model::RegisterResult registerUser(model::User&);
|
||||
|
||||
bool doesUserExist(const model::User&);
|
||||
bool validatePassword(const model::User&);
|
||||
|
||||
void printUser(const model::User&);
|
||||
private:
|
||||
model::BinaryPath m_bConf;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,25 +0,0 @@
|
||||
#ifndef YEARMANAGER_H_
|
||||
#define YEARMANAGER_H_
|
||||
|
||||
#include "model/Models.h"
|
||||
|
||||
namespace manager
|
||||
{
|
||||
class YearManager
|
||||
{
|
||||
public:
|
||||
YearManager(const model::BinaryPath&);
|
||||
|
||||
model::Year retrieveYear(model::Year&);
|
||||
model::Year saveYear(const model::Song&);
|
||||
|
||||
void deleteYear(const model::Song&);
|
||||
void updateYear(model::Song&, const model::Song&);
|
||||
|
||||
static void printYear(const model::Year&);
|
||||
private:
|
||||
model::BinaryPath m_bConf;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,176 +0,0 @@
|
||||
#ifndef MODELS_H_
|
||||
#define MODELS_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace model {
|
||||
struct Song {
|
||||
Song() = default;
|
||||
Song(const int id) : id(id) { }
|
||||
|
||||
int id;
|
||||
std::string title;
|
||||
std::string artist;
|
||||
std::string album;
|
||||
std::string genre;
|
||||
int year;
|
||||
int duration;
|
||||
int track;
|
||||
int disc;
|
||||
std::string songPath;
|
||||
std::vector<unsigned char> data;
|
||||
int coverArtId;
|
||||
int artistId;
|
||||
int albumId;
|
||||
int genreId;
|
||||
int yearId;
|
||||
};
|
||||
|
||||
struct Artist {
|
||||
Artist() = default;
|
||||
Artist(const Song& song)
|
||||
{
|
||||
id = song.artistId;
|
||||
artist = song.artist;
|
||||
}
|
||||
Artist(const int id) : id(id) { }
|
||||
|
||||
int id;
|
||||
std::string artist;
|
||||
};
|
||||
|
||||
struct Album {
|
||||
Album() = default;
|
||||
Album(const Song& song)
|
||||
{
|
||||
id = song.albumId;
|
||||
title = song.album;
|
||||
year = song.year;
|
||||
}
|
||||
Album(const int id) : id(id) { }
|
||||
|
||||
int id;
|
||||
std::string title;
|
||||
int year;
|
||||
std::vector<Song> songs;
|
||||
};
|
||||
|
||||
struct Genre {
|
||||
Genre() = default;
|
||||
Genre(const Song& song)
|
||||
{
|
||||
id = song.genreId;
|
||||
category = song.genre;
|
||||
}
|
||||
Genre(const int id) : id(id) { }
|
||||
|
||||
int id;
|
||||
std::string category;
|
||||
|
||||
};
|
||||
|
||||
struct Year {
|
||||
Year() = default;
|
||||
Year(const Song& song)
|
||||
{
|
||||
id = song.yearId;
|
||||
year = song.year;
|
||||
}
|
||||
Year(const int id) : id(id) { }
|
||||
|
||||
int id;
|
||||
int year;
|
||||
};
|
||||
|
||||
struct Cover {
|
||||
Cover() = default;
|
||||
Cover(const Song& song)
|
||||
{
|
||||
id = song.coverArtId;
|
||||
songTitle = song.title;
|
||||
}
|
||||
Cover(const int id) : id(id) { }
|
||||
|
||||
int id;
|
||||
std::string songTitle;
|
||||
std::string imagePath;
|
||||
// Not being used but it should be
|
||||
std::vector<unsigned char> data;
|
||||
};
|
||||
|
||||
class Token {
|
||||
public:
|
||||
Token() = default;
|
||||
Token(const std::string& accessToken) :
|
||||
accessToken(accessToken) { }
|
||||
Token(const std::string& accessToken, const std::string& tokenType,
|
||||
const int expiration) :
|
||||
accessToken(accessToken), tokenType(tokenType),
|
||||
expiration(expiration) { }
|
||||
|
||||
std::string accessToken;
|
||||
int expiration;
|
||||
std::string tokenType;
|
||||
};
|
||||
|
||||
struct LoginResult {
|
||||
int userId;
|
||||
std::string username;
|
||||
std::string accessToken;
|
||||
std::string tokenType;
|
||||
std::string message;
|
||||
int expiration;
|
||||
};
|
||||
|
||||
class RegisterResult {
|
||||
public:
|
||||
std::string username;
|
||||
bool registered;
|
||||
std::string message;
|
||||
};
|
||||
|
||||
struct User {
|
||||
int id;
|
||||
std::string firstname;
|
||||
std::string lastname;
|
||||
std::string email;
|
||||
std::string phone;
|
||||
std::string username;
|
||||
std::string password;
|
||||
};
|
||||
|
||||
struct PassSec {
|
||||
int id;
|
||||
std::string hashPassword;
|
||||
std::string salt;
|
||||
int userId;
|
||||
};
|
||||
|
||||
struct AuthCredentials {
|
||||
std::string domain;
|
||||
std::string apiIdentifier;
|
||||
std::string clientId;
|
||||
std::string clientSecret;
|
||||
std::string uri;
|
||||
std::string endpoint;
|
||||
};
|
||||
|
||||
struct DatabaseConnection {
|
||||
std::string server;
|
||||
std::string username;
|
||||
std::string password;
|
||||
std::string database;
|
||||
};
|
||||
|
||||
struct BinaryPath {
|
||||
BinaryPath() = default;
|
||||
BinaryPath(const char *p) : path(std::move(p)) { }
|
||||
BinaryPath(std::string& p) : path(p) { }
|
||||
BinaryPath(const std::string& p) : path(p) { }
|
||||
|
||||
std::string path;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,14 +0,0 @@
|
||||
#ifndef ALBUMFILTER_H_
|
||||
#define ALBUMFILTER_H_
|
||||
|
||||
namespace type
|
||||
{
|
||||
enum class AlbumFilter
|
||||
{
|
||||
id = 0,
|
||||
title,
|
||||
year
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,13 +0,0 @@
|
||||
#ifndef ARTISTFILTER_H_
|
||||
#define ARTISTFILTER_H_
|
||||
|
||||
namespace type
|
||||
{
|
||||
enum class ArtistFilter
|
||||
{
|
||||
id = 0,
|
||||
artist
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,14 +0,0 @@
|
||||
#ifndef COVERFILTER_H_
|
||||
#define COVERFILTER_H_
|
||||
|
||||
namespace type
|
||||
{
|
||||
enum class CoverFilter
|
||||
{
|
||||
id = 0,
|
||||
songTitle,
|
||||
imagePath
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,13 +0,0 @@
|
||||
#ifndef GENREFILTER_H_
|
||||
#define GENREFILTER_H_
|
||||
|
||||
namespace type
|
||||
{
|
||||
enum class GenreFilter
|
||||
{
|
||||
id = 0,
|
||||
category
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,15 +0,0 @@
|
||||
#ifndef PATHTYPE_H_
|
||||
#define PATHTYPE_H_
|
||||
|
||||
namespace type
|
||||
{
|
||||
enum PathType
|
||||
{
|
||||
music = 0,
|
||||
archive,
|
||||
temp,
|
||||
coverArt
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,13 +0,0 @@
|
||||
#ifndef SALTFILTER_H_
|
||||
#define SALTFILTER_H_
|
||||
|
||||
namespace type {
|
||||
enum class SaltFilter
|
||||
{
|
||||
id = 0,
|
||||
salt,
|
||||
userId
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,23 +0,0 @@
|
||||
#ifndef SCOPES_H_
|
||||
#define SCOPES_H_
|
||||
|
||||
namespace type
|
||||
{
|
||||
enum class Scope
|
||||
{
|
||||
upload = 0,
|
||||
download,
|
||||
stream,
|
||||
retrieveSong,
|
||||
updateSong,
|
||||
deleteSong,
|
||||
retrieveAlbum,
|
||||
retrieveArtist,
|
||||
retrieveGenre,
|
||||
retrieveYear,
|
||||
downloadCoverArt
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,16 +0,0 @@
|
||||
#ifndef SONGCHANGED_H_
|
||||
#define SONGCHANGED_H_
|
||||
|
||||
namespace type
|
||||
{
|
||||
enum SongChanged
|
||||
{
|
||||
title = 0,
|
||||
artist,
|
||||
album,
|
||||
genre,
|
||||
year
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,17 +0,0 @@
|
||||
#ifndef SONGFILTER_H_
|
||||
#define SONGFILTER_H_
|
||||
|
||||
namespace type
|
||||
{
|
||||
enum class SongFilter
|
||||
{
|
||||
id = 0,
|
||||
title,
|
||||
album,
|
||||
artist,
|
||||
genre,
|
||||
year
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,12 +0,0 @@
|
||||
#ifndef USERFILTER_H_
|
||||
#define USERFILTER_H_
|
||||
|
||||
namespace type {
|
||||
enum class UserFilter
|
||||
{
|
||||
id = 0,
|
||||
username
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,13 +0,0 @@
|
||||
#ifndef YEARFILTER_H_
|
||||
#define YEARFILTER_H_
|
||||
|
||||
namespace type
|
||||
{
|
||||
enum class YearFilter
|
||||
{
|
||||
id = 0,
|
||||
year
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,32 +0,0 @@
|
||||
#ifndef IMAGEFILE_H_
|
||||
#define IMAGEFILE_H_
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include <attachedpictureframe.h>
|
||||
#include <tag.h>
|
||||
#include <tfile.h>
|
||||
#include <tfilestream.h>
|
||||
#include <fileref.h>
|
||||
#include <tbytevector.h>
|
||||
#include <tbytevectorstream.h>
|
||||
#include <tpropertymap.h>
|
||||
#include <id3v2tag.h>
|
||||
|
||||
namespace utility
|
||||
{
|
||||
class ImageFile : public TagLib::File
|
||||
{
|
||||
public:
|
||||
ImageFile(const char *file);
|
||||
|
||||
TagLib::ByteVector data();
|
||||
|
||||
private:
|
||||
virtual TagLib::Tag *tag() const { return 0; }
|
||||
virtual TagLib::AudioProperties *audioProperties() const { return 0; }
|
||||
virtual bool save() { return false; }
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,26 +0,0 @@
|
||||
#ifndef METADATA_RETRIEVER_H_
|
||||
#define METADATA_RETRIEVER_H_
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
#include "model/Models.h"
|
||||
|
||||
namespace utility {
|
||||
class MetadataRetriever
|
||||
{
|
||||
public:
|
||||
model::Song retrieveMetadata(std::string&);
|
||||
|
||||
model::Cover updateCoverArt(const model::Song&, model::Cover&, const std::string&);
|
||||
model::Cover applyStockCoverArt(const model::Song&, model::Cover&, const std::string&);
|
||||
model::Cover applyCoverArt(const model::Song&, model::Cover&);
|
||||
|
||||
bool songContainsCoverArt(const model::Song&);
|
||||
|
||||
void updateMetadata(model::Song&, const model::Song&);
|
||||
private:
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,19 +0,0 @@
|
||||
#ifndef PASSWORDENCRYPTION_H_
|
||||
#define PASSWORDENCRYPTION_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "model/Models.h"
|
||||
|
||||
namespace utility {
|
||||
class PasswordEncryption {
|
||||
public:
|
||||
model::PassSec hashPassword(const model::User&);
|
||||
|
||||
bool isPasswordValid(const model::User&, const model::PassSec&);
|
||||
private:
|
||||
int saltSize();
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,25 +0,0 @@
|
||||
#ifndef INITIALIZATION_H_
|
||||
#define INITIALIZATION_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "model/Models.h"
|
||||
|
||||
namespace verify
|
||||
{
|
||||
class Initialization
|
||||
{
|
||||
public:
|
||||
static bool skipVerification(const std::string&);
|
||||
|
||||
static void checkIcarus(const model::BinaryPath&);
|
||||
private:
|
||||
static bool confirmConfigAuth(const model::BinaryPath&);
|
||||
static bool confirmConfigDatabase(const model::BinaryPath&);
|
||||
static bool confirmConfigPaths(const model::BinaryPath&);
|
||||
|
||||
static void failedConfirmation();
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,62 @@
|
||||
-- Add migration script here
|
||||
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
||||
|
||||
-- Table to store queued songs to process
|
||||
CREATE TABLE IF NOT EXISTS "songQueue" (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
filename TEXT NOT NULL,
|
||||
status TEXT CHECK (status IN ('pending', 'ready', 'processing', 'done')),
|
||||
data BYTEA NULL,
|
||||
user_id UUID NULL
|
||||
);
|
||||
|
||||
-- Table to store queued metadata
|
||||
CREATE TABLE IF NOT EXISTS "metadataQueue" (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
metadata jsonb NOT NULL,
|
||||
created_at timestamptz DEFAULT now(),
|
||||
song_queue_id UUID NOT NULL
|
||||
);
|
||||
|
||||
-- Table to store queued coverart
|
||||
CREATE TABLE IF NOT EXISTS "coverartQueue" (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
data BYTEA NULL,
|
||||
song_queue_id UUID NULL
|
||||
);
|
||||
|
||||
-- Create an index for better query performance
|
||||
CREATE INDEX metadata_queue_data_metadata ON "metadataQueue" USING gin (metadata);
|
||||
|
||||
-- Table to store a song's info
|
||||
CREATE TABLE IF NOT EXISTS "song" (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
title TEXT NOT NULL,
|
||||
artist TEXT NOT NULL,
|
||||
album_artist TEXT NOT NULL,
|
||||
album TEXT NOT NULL,
|
||||
genre TEXT NOT NULL,
|
||||
-- TODO: Address discrepancy of date and year at some point
|
||||
-- date TEXT NOT NULL,
|
||||
year INT NOT NULL,
|
||||
track INT NOT NULL,
|
||||
disc INT NOT NULL,
|
||||
track_count INT NOT NULL,
|
||||
disc_count INT NOT NULL,
|
||||
duration INT NOT NULL,
|
||||
audio_type TEXT NOT NULL,
|
||||
date_created timestamptz DEFAULT now(),
|
||||
filename TEXT NOT NULL,
|
||||
directory TEXT NOT NULL,
|
||||
user_id UUID NULL
|
||||
-- TODO: Add coverart id later. This will allow multiple songs to be linked to a single cover art
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "coverart" (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
title TEXT NOT NULL,
|
||||
-- TODO: Separate path later
|
||||
path TEXT NOT NULL,
|
||||
song_id UUID NOT NULL
|
||||
-- TODO: Add type later
|
||||
);
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"root_music_path": "/icarus/music/",
|
||||
"temp_root_path": "/icarus/temp/",
|
||||
"cover_root_path": "/icarus/cover/",
|
||||
"archive_root_path": "icarus/archive/"
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
#include <exception>
|
||||
#include <iostream>
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include <mysql/mysql.h>
|
||||
#include <oatpp/network/server/Server.hpp>
|
||||
#include "oatpp/network/server/SimpleTCPConnectionProvider.hpp"
|
||||
#include "oatpp/web/server/HttpConnectionHandler.hpp"
|
||||
|
||||
#include "component/AppComponent.hpp"
|
||||
#include "controller/ArtistController.hpp"
|
||||
#include "controller/AlbumController.hpp"
|
||||
#include "controller/CoverArtController.hpp"
|
||||
#include "controller/GenreController.hpp"
|
||||
#include "controller/LoginController.hpp"
|
||||
#include "controller/RegisterController.hpp"
|
||||
#include "controller/SongController.hpp"
|
||||
#include "controller/YearController.hpp"
|
||||
#include "model/Models.h"
|
||||
#include "verify/Initialization.h"
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
void run(const model::BinaryPath& bConf)
|
||||
{
|
||||
component::AppComponent component;
|
||||
|
||||
OATPP_COMPONENT(std::shared_ptr<oatpp::web::server::HttpRouter>, router);
|
||||
|
||||
auto albumController = std::make_shared<controller::AlbumController>(bConf);
|
||||
auto artistController = std::make_shared<controller::ArtistController>(bConf);
|
||||
auto coverArtController = std::make_shared<controller::CoverArtController>(bConf);
|
||||
auto gnrController = std::make_shared<controller::GenreController>(bConf);
|
||||
auto logController = std::make_shared<controller::LoginController>(bConf);
|
||||
auto regController = std::make_shared<controller::RegisterController>(bConf);
|
||||
auto sngController = std::make_shared<controller::SongController>(bConf);
|
||||
auto yearController = std::make_shared<controller::YearController>(bConf);
|
||||
|
||||
albumController->addEndpointsToRouter(router);
|
||||
artistController->addEndpointsToRouter(router);
|
||||
coverArtController->addEndpointsToRouter(router);
|
||||
gnrController->addEndpointsToRouter(router);
|
||||
logController->addEndpointsToRouter(router);
|
||||
regController->addEndpointsToRouter(router);
|
||||
sngController->addEndpointsToRouter(router);
|
||||
yearController->addEndpointsToRouter(router);
|
||||
|
||||
OATPP_COMPONENT(std::shared_ptr<oatpp::network::server::ConnectionHandler>, connectionHandler);
|
||||
|
||||
OATPP_COMPONENT(std::shared_ptr<oatpp::network::ServerConnectionProvider>, connectionProvider);
|
||||
|
||||
oatpp::network::server::Server server(connectionProvider, connectionHandler);
|
||||
|
||||
OATPP_LOGI("icarus", "Server running on port %s", connectionProvider->getProperty("port").getData());
|
||||
|
||||
server.run();
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
oatpp::base::Environment::init();
|
||||
|
||||
model::BinaryPath bConf(std::move(argv[0]));
|
||||
|
||||
if (argc > 1) {
|
||||
if (!verify::Initialization::skipVerification(argv[1])) {
|
||||
verify::Initialization::checkIcarus(bConf);
|
||||
} else {
|
||||
std::cout << "skiping verifyication" << std::endl;
|
||||
}
|
||||
} else {
|
||||
verify::Initialization::checkIcarus(bConf);
|
||||
}
|
||||
|
||||
run(bConf);
|
||||
|
||||
oatpp::base::Environment::destroy();
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
// use std::collections::BTreeMap;
|
||||
// use std::sync::Arc;
|
||||
|
||||
use axum::{
|
||||
Json,
|
||||
http::{Request, StatusCode},
|
||||
middleware::Next,
|
||||
response::IntoResponse,
|
||||
};
|
||||
use axum_extra::extract::cookie::CookieJar;
|
||||
use jsonwebtoken::{DecodingKey, Validation, decode};
|
||||
use serde::{Deserialize, Serialize};
|
||||
// use serde_json::{json, Value};
|
||||
use thiserror::Error;
|
||||
// use time::OffsetDateTime;
|
||||
|
||||
fn deserialize_i64_from_f64<'de, D>(deserializer: D) -> Result<i64, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let val = f64::deserialize(deserializer)?;
|
||||
// Handle NaN and infinity cases
|
||||
if val.is_nan() || val.is_infinite() {
|
||||
return Err(serde::de::Error::custom("invalid float value"));
|
||||
}
|
||||
// Round to nearest integer and convert
|
||||
let rounded = val.round();
|
||||
// Check if the rounded value can fit in i64
|
||||
if rounded < (i64::MIN as f64) || rounded > (i64::MAX as f64) {
|
||||
return Err(serde::de::Error::custom("float out of i64 range"));
|
||||
}
|
||||
Ok(rounded as i64)
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct UserClaims {
|
||||
pub iss: String,
|
||||
pub aud: String, // Audience
|
||||
pub sub: String, // Subject (user ID)
|
||||
#[serde(deserialize_with = "deserialize_i64_from_f64")]
|
||||
pub exp: i64, // Expiration time (UTC timestamp)
|
||||
#[serde(deserialize_with = "deserialize_i64_from_f64")]
|
||||
pub iat: i64, // Issued at (UTC timestamp)
|
||||
// pub azp: String,
|
||||
// pub gty: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub roles: Option<Vec<String>>, // Optional roles
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum JwtError {
|
||||
#[error("Token creation failed")]
|
||||
TokenCreation,
|
||||
#[error("Token verification failed")]
|
||||
TokenVerification,
|
||||
#[error("Invalid token")]
|
||||
InvalidToken,
|
||||
#[error("Token expired")]
|
||||
ExpiredToken,
|
||||
#[error("Invalid key")]
|
||||
InvalidKey,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct ErrorResponse {
|
||||
pub status: &'static str,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
pub async fn auth<B>(
|
||||
cookie_jar: CookieJar,
|
||||
req: Request<axum::body::Body>,
|
||||
next: Next,
|
||||
) -> Result<impl IntoResponse, (StatusCode, Json<ErrorResponse>)> {
|
||||
println!("Cookie: {cookie_jar:?}");
|
||||
|
||||
let token = cookie_jar
|
||||
.get("token")
|
||||
.map(|cookie| cookie.value().to_string())
|
||||
.or_else(|| {
|
||||
req.headers()
|
||||
.get(axum::http::header::AUTHORIZATION)
|
||||
.and_then(|auth_header| auth_header.to_str().ok())
|
||||
.and_then(|auth_value| {
|
||||
println!("Auth value: {auth_value:?}");
|
||||
auth_value.strip_prefix("Bearer ").map(String::from)
|
||||
})
|
||||
});
|
||||
|
||||
let token = token.ok_or_else(|| {
|
||||
let json_error = ErrorResponse {
|
||||
status: "fail",
|
||||
message: "You are not logged in, please provide token".to_string(),
|
||||
};
|
||||
(StatusCode::UNAUTHORIZED, Json(json_error))
|
||||
})?;
|
||||
|
||||
let secret_key = icarus_envy::environment::get_secret_main_key().await;
|
||||
|
||||
let mut validation = Validation::new(jsonwebtoken::Algorithm::HS256);
|
||||
validation.set_audience(&["icarus"]); // Must match exactly what's in the token
|
||||
let claims = decode::<UserClaims>(
|
||||
&token,
|
||||
// TODO: Replace with code to get secret from env
|
||||
&DecodingKey::from_secret(secret_key.as_ref()),
|
||||
&validation,
|
||||
)
|
||||
.map_err(|err| {
|
||||
eprintln!("Error: {err:?}");
|
||||
let json_error = ErrorResponse {
|
||||
status: "fail",
|
||||
message: "Invalid token - Error decoding claims".to_string(),
|
||||
};
|
||||
(StatusCode::UNAUTHORIZED, Json(json_error))
|
||||
})?
|
||||
.claims;
|
||||
|
||||
println!("Claims: {claims:?}");
|
||||
|
||||
Ok(next.run(req).await)
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
#include "callback/StreamCallback.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
|
||||
namespace callback {
|
||||
StreamCallback::StreamCallback() :
|
||||
m_counter(0) { }
|
||||
|
||||
StreamCallback::StreamCallback(const std::string& songPath) :
|
||||
m_songPath(songPath),
|
||||
m_counter(0),
|
||||
m_bytesRead(0)
|
||||
{
|
||||
// getting file size
|
||||
std::ifstream song(m_songPath.c_str(), std::ios::binary | std::ios::in | std::ios::ate);
|
||||
m_fileSize = song.tellg();
|
||||
std::cout << "file size is " << m_fileSize << " bytes" << std::endl;
|
||||
}
|
||||
|
||||
|
||||
oatpp::data::v_io_size StreamCallback::read(void *buff, oatpp::data::v_io_size count)
|
||||
{
|
||||
if (m_counter >= m_fileSize) {
|
||||
std::cout << "done streaming song" << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::fstream song(m_songPath.c_str(), std::ios::binary | std::ios::in);
|
||||
song.seekg(m_counter);
|
||||
|
||||
std::unique_ptr<char[]> data(new char[count]);
|
||||
|
||||
song.read(data.get(), count);
|
||||
std::memcpy(buff, data.get(), count);
|
||||
|
||||
m_counter += count;
|
||||
|
||||
song.close();
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,767 @@
|
||||
// TODO: Separate queue and coverart endpoints
|
||||
#[derive(Debug, Default, serde::Deserialize, serde::Serialize)]
|
||||
pub struct CoverArtQueue {
|
||||
pub id: uuid::Uuid,
|
||||
pub song_queue_id: uuid::Uuid,
|
||||
}
|
||||
|
||||
pub mod request {
|
||||
|
||||
pub mod link {
|
||||
#[derive(Debug, Default, serde::Deserialize, serde::Serialize)]
|
||||
pub struct Request {
|
||||
pub coverart_id: uuid::Uuid,
|
||||
pub song_queue_id: uuid::Uuid,
|
||||
}
|
||||
}
|
||||
|
||||
pub mod fetch_coverart_no_data {
|
||||
#[derive(Debug, Default, serde::Deserialize, serde::Serialize)]
|
||||
pub struct Params {
|
||||
pub id: Option<uuid::Uuid>,
|
||||
pub song_queue_id: Option<uuid::Uuid>,
|
||||
}
|
||||
}
|
||||
|
||||
pub mod fetch_coverart_with_data {
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub struct Params {
|
||||
pub id: Option<uuid::Uuid>,
|
||||
pub song_queue_id: Option<uuid::Uuid>,
|
||||
}
|
||||
}
|
||||
|
||||
pub mod create_coverart {
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub struct Request {
|
||||
pub song_id: uuid::Uuid,
|
||||
pub coverart_queue_id: uuid::Uuid,
|
||||
}
|
||||
}
|
||||
|
||||
pub mod wipe_data_from_coverart_queue {
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub struct Request {
|
||||
pub coverart_queue_id: uuid::Uuid,
|
||||
}
|
||||
}
|
||||
|
||||
pub mod get_coverart {
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub struct Params {
|
||||
pub id: Option<uuid::Uuid>,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod response {
|
||||
#[derive(Debug, Default, serde::Deserialize, serde::Serialize)]
|
||||
pub struct Response {
|
||||
pub message: String,
|
||||
pub data: Vec<uuid::Uuid>,
|
||||
}
|
||||
|
||||
pub mod link {
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub struct Id {
|
||||
pub coverart_id: uuid::Uuid,
|
||||
pub song_queue_id: uuid::Uuid,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, serde::Deserialize, serde::Serialize)]
|
||||
pub struct Response {
|
||||
pub message: String,
|
||||
pub data: Vec<Id>,
|
||||
}
|
||||
}
|
||||
|
||||
pub mod fetch_coverart_no_data {
|
||||
#[derive(Debug, Default, serde::Deserialize, serde::Serialize)]
|
||||
pub struct Response {
|
||||
pub message: String,
|
||||
pub data: Vec<super::super::CoverArtQueue>,
|
||||
}
|
||||
}
|
||||
|
||||
pub mod fetch_coverart_with_data {
|
||||
#[derive(Debug, Default, serde::Deserialize, serde::Serialize)]
|
||||
pub struct Response {
|
||||
pub message: String,
|
||||
pub data: Vec<Vec<u8>>,
|
||||
}
|
||||
}
|
||||
|
||||
pub mod create_coverart {
|
||||
#[derive(Debug, Default, serde::Deserialize, serde::Serialize)]
|
||||
pub struct Response {
|
||||
pub message: String,
|
||||
pub data: Vec<icarus_models::coverart::CoverArt>,
|
||||
}
|
||||
}
|
||||
|
||||
pub mod wipe_data_from_coverart_queue {
|
||||
#[derive(Debug, Default, serde::Deserialize, serde::Serialize)]
|
||||
pub struct Response {
|
||||
pub message: String,
|
||||
pub data: Vec<uuid::Uuid>,
|
||||
}
|
||||
}
|
||||
|
||||
pub mod get_coverart {
|
||||
#[derive(Debug, Default, serde::Deserialize, serde::Serialize)]
|
||||
pub struct Response {
|
||||
pub message: String,
|
||||
pub data: Vec<icarus_models::coverart::CoverArt>,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod db {
|
||||
use sqlx::Row;
|
||||
|
||||
pub async fn insert(pool: &sqlx::PgPool, data: &Vec<u8>) -> Result<uuid::Uuid, sqlx::Error> {
|
||||
let result = sqlx::query(
|
||||
r#"
|
||||
INSERT INTO "coverartQueue" (data) VALUES($1) RETURNING id;
|
||||
"#,
|
||||
)
|
||||
.bind(data)
|
||||
.fetch_one(pool)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
eprintln!("Error inserting: {e:?}");
|
||||
});
|
||||
|
||||
match result {
|
||||
Ok(row) => {
|
||||
let id: uuid::Uuid = row
|
||||
.try_get("id")
|
||||
.map_err(|_e| sqlx::Error::RowNotFound)
|
||||
.unwrap();
|
||||
Ok(id)
|
||||
}
|
||||
Err(_err) => Err(sqlx::Error::RowNotFound),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn update(
|
||||
pool: &sqlx::PgPool,
|
||||
coverart_id: &uuid::Uuid,
|
||||
song_queue_id: &uuid::Uuid,
|
||||
) -> Result<i32, sqlx::Error> {
|
||||
let result = sqlx::query(
|
||||
r#"
|
||||
UPDATE "coverartQueue" SET song_queue_id = $1 WHERE id = $2;
|
||||
"#,
|
||||
)
|
||||
.bind(song_queue_id)
|
||||
.bind(coverart_id)
|
||||
.execute(pool)
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Ok(_) => Ok(0),
|
||||
Err(_err) => Err(sqlx::Error::RowNotFound),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_coverart_queue_with_id(
|
||||
pool: &sqlx::PgPool,
|
||||
id: &uuid::Uuid,
|
||||
) -> Result<super::CoverArtQueue, sqlx::Error> {
|
||||
let result = sqlx::query(
|
||||
r#"
|
||||
SELECT id, song_queue_id FROM "coverartQueue" WHERE id = $1;
|
||||
"#,
|
||||
)
|
||||
.bind(id)
|
||||
.fetch_one(pool)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
eprintln!("Error querying data: {e:?}");
|
||||
});
|
||||
|
||||
match result {
|
||||
Ok(row) => Ok(super::CoverArtQueue {
|
||||
id: row
|
||||
.try_get("id")
|
||||
.map_err(|_e| sqlx::Error::RowNotFound)
|
||||
.unwrap(),
|
||||
song_queue_id: row
|
||||
.try_get("song_queue_id")
|
||||
.map_err(|_e| sqlx::Error::RowNotFound)
|
||||
.unwrap(),
|
||||
}),
|
||||
Err(_) => Err(sqlx::Error::RowNotFound),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_coverart_queue_with_song_queue_id(
|
||||
pool: &sqlx::PgPool,
|
||||
song_queue_id: &uuid::Uuid,
|
||||
) -> Result<super::CoverArtQueue, sqlx::Error> {
|
||||
let result = sqlx::query(
|
||||
r#"
|
||||
SELECT id, song_queue_id 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(super::CoverArtQueue {
|
||||
id: row
|
||||
.try_get("id")
|
||||
.map_err(|_e| sqlx::Error::RowNotFound)
|
||||
.unwrap(),
|
||||
song_queue_id: row
|
||||
.try_get("song_queue_id")
|
||||
.map_err(|_e| sqlx::Error::RowNotFound)
|
||||
.unwrap(),
|
||||
}),
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod cov_db {
|
||||
use sqlx::Row;
|
||||
|
||||
pub async fn create(
|
||||
pool: &sqlx::PgPool,
|
||||
coverart: &icarus_models::coverart::CoverArt,
|
||||
song_id: &uuid::Uuid,
|
||||
) -> Result<uuid::Uuid, sqlx::Error> {
|
||||
let result = sqlx::query(
|
||||
r#"
|
||||
INSERT INTO "coverart" (title, path, song_id) VALUES($1, $2, $3) RETURNING id;
|
||||
"#,
|
||||
)
|
||||
.bind(&coverart.title)
|
||||
.bind(&coverart.path)
|
||||
.bind(song_id)
|
||||
.fetch_one(pool)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
eprintln!("Error inserting: {e}");
|
||||
});
|
||||
|
||||
match result {
|
||||
Ok(row) => {
|
||||
let id: uuid::Uuid = row
|
||||
.try_get("id")
|
||||
.map_err(|_e| sqlx::Error::RowNotFound)
|
||||
.unwrap();
|
||||
Ok(id)
|
||||
}
|
||||
Err(_err) => Err(sqlx::Error::RowNotFound),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_coverart(
|
||||
pool: &sqlx::PgPool,
|
||||
id: &uuid::Uuid,
|
||||
) -> Result<icarus_models::coverart::CoverArt, sqlx::Error> {
|
||||
let result = sqlx::query(
|
||||
r#"
|
||||
SELECT id, title, path, song_id FROM "coverart" WHERE id = $1;
|
||||
"#,
|
||||
)
|
||||
.bind(id)
|
||||
.fetch_one(pool)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
eprintln!("Error querying data: {e:?}");
|
||||
});
|
||||
|
||||
match result {
|
||||
Ok(row) => Ok(icarus_models::coverart::CoverArt {
|
||||
id: row
|
||||
.try_get("id")
|
||||
.map_err(|_e| sqlx::Error::RowNotFound)
|
||||
.unwrap(),
|
||||
title: row
|
||||
.try_get("title")
|
||||
.map_err(|_e| sqlx::Error::RowNotFound)
|
||||
.unwrap(),
|
||||
path: row
|
||||
.try_get("path")
|
||||
.map_err(|_e| sqlx::Error::RowNotFound)
|
||||
.unwrap(),
|
||||
data: Vec::new(),
|
||||
song_id: row
|
||||
.try_get("song_id")
|
||||
.map_err(|_e| sqlx::Error::RowNotFound)
|
||||
.unwrap(),
|
||||
}),
|
||||
Err(_) => Err(sqlx::Error::RowNotFound),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_coverart_with_song_id(
|
||||
pool: &sqlx::PgPool,
|
||||
song_id: &uuid::Uuid,
|
||||
) -> Result<icarus_models::coverart::CoverArt, sqlx::Error> {
|
||||
let result = sqlx::query(
|
||||
r#"
|
||||
SELECT id, title, path, song_id FROM "coverart" WHERE song_id = $1;
|
||||
"#,
|
||||
)
|
||||
.bind(song_id)
|
||||
.fetch_one(pool)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
eprintln!("Error querying data: {e:?}");
|
||||
});
|
||||
|
||||
match result {
|
||||
Ok(row) => Ok(icarus_models::coverart::CoverArt {
|
||||
id: row
|
||||
.try_get("id")
|
||||
.map_err(|_e| sqlx::Error::RowNotFound)
|
||||
.unwrap(),
|
||||
title: row
|
||||
.try_get("title")
|
||||
.map_err(|_e| sqlx::Error::RowNotFound)
|
||||
.unwrap(),
|
||||
path: row
|
||||
.try_get("path")
|
||||
.map_err(|_e| sqlx::Error::RowNotFound)
|
||||
.unwrap(),
|
||||
data: Vec::new(),
|
||||
song_id: row
|
||||
.try_get("song_id")
|
||||
.map_err(|_e| sqlx::Error::RowNotFound)
|
||||
.unwrap(),
|
||||
}),
|
||||
Err(_) => Err(sqlx::Error::RowNotFound),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn delete_coverart(
|
||||
pool: &sqlx::PgPool,
|
||||
id: &uuid::Uuid,
|
||||
) -> Result<icarus_models::coverart::CoverArt, sqlx::Error> {
|
||||
let result = sqlx::query(
|
||||
r#"
|
||||
DELETE FROM "coverart"
|
||||
WHERE id = $1
|
||||
RETURNING id, title, path, song_id
|
||||
"#,
|
||||
)
|
||||
.bind(id)
|
||||
.fetch_one(pool)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
eprintln!("Error deleting data: {e:?}");
|
||||
});
|
||||
|
||||
match result {
|
||||
Ok(row) => Ok(icarus_models::coverart::CoverArt {
|
||||
id: row
|
||||
.try_get("id")
|
||||
.map_err(|_e| sqlx::Error::RowNotFound)
|
||||
.unwrap(),
|
||||
title: row
|
||||
.try_get("title")
|
||||
.map_err(|_e| sqlx::Error::RowNotFound)
|
||||
.unwrap(),
|
||||
path: row
|
||||
.try_get("path")
|
||||
.map_err(|_e| sqlx::Error::RowNotFound)
|
||||
.unwrap(),
|
||||
song_id: row
|
||||
.try_get("song_id")
|
||||
.map_err(|_e| sqlx::Error::RowNotFound)
|
||||
.unwrap(),
|
||||
data: Vec::new(),
|
||||
}),
|
||||
Err(_err) => Err(sqlx::Error::RowNotFound),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod endpoint {
|
||||
use std::io::Write;
|
||||
|
||||
use axum::response::IntoResponse;
|
||||
|
||||
pub async fn queue(
|
||||
axum::Extension(pool): axum::Extension<sqlx::PgPool>,
|
||||
mut multipart: axum::extract::Multipart,
|
||||
) -> (
|
||||
axum::http::StatusCode,
|
||||
axum::Json<super::response::Response>,
|
||||
) {
|
||||
let mut response = super::response::Response::default();
|
||||
|
||||
match multipart.next_field().await {
|
||||
Ok(Some(field)) => {
|
||||
let name = field.name().unwrap().to_string();
|
||||
let file_name = field.file_name().unwrap().to_string();
|
||||
let content_type = field.content_type().unwrap().to_string();
|
||||
let data = field.bytes().await.unwrap();
|
||||
let raw_data = data.to_vec();
|
||||
|
||||
println!(
|
||||
"Received file '{}' (name = '{}', content-type = '{}', size = {})",
|
||||
file_name,
|
||||
name,
|
||||
content_type,
|
||||
data.len()
|
||||
);
|
||||
|
||||
match super::db::insert(&pool, &raw_data).await {
|
||||
Ok(id) => {
|
||||
response.message = String::from("Successful");
|
||||
response.data.push(id);
|
||||
(axum::http::StatusCode::OK, axum::Json(response))
|
||||
}
|
||||
Err(err) => {
|
||||
response.message = err.to_string();
|
||||
(axum::http::StatusCode::BAD_REQUEST, axum::Json(response))
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(None) => (axum::http::StatusCode::BAD_REQUEST, axum::Json(response)),
|
||||
Err(err) => {
|
||||
response.message = err.to_string();
|
||||
(axum::http::StatusCode::BAD_REQUEST, axum::Json(response))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn link(
|
||||
axum::Extension(pool): axum::Extension<sqlx::PgPool>,
|
||||
axum::Json(payload): axum::Json<super::request::link::Request>,
|
||||
) -> (
|
||||
axum::http::StatusCode,
|
||||
axum::Json<super::response::link::Response>,
|
||||
) {
|
||||
let mut response = super::response::link::Response::default();
|
||||
let id = payload.coverart_id;
|
||||
let song_id = payload.song_queue_id;
|
||||
|
||||
match super::db::update(&pool, &id, &song_id).await {
|
||||
Ok(_o) => {
|
||||
response.data.push(super::response::link::Id {
|
||||
song_queue_id: song_id,
|
||||
coverart_id: id,
|
||||
});
|
||||
|
||||
(axum::http::StatusCode::OK, axum::Json(response))
|
||||
}
|
||||
Err(err) => {
|
||||
response.message = err.to_string();
|
||||
(axum::http::StatusCode::BAD_REQUEST, axum::Json(response))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn fetch_coverart_no_data(
|
||||
axum::Extension(pool): axum::Extension<sqlx::PgPool>,
|
||||
axum::extract::Query(params): axum::extract::Query<
|
||||
super::request::fetch_coverart_no_data::Params,
|
||||
>,
|
||||
) -> (
|
||||
axum::http::StatusCode,
|
||||
axum::Json<super::response::fetch_coverart_no_data::Response>,
|
||||
) {
|
||||
let mut response = super::response::fetch_coverart_no_data::Response::default();
|
||||
|
||||
match params.id {
|
||||
Some(id) => match super::db::get_coverart_queue_with_id(&pool, &id).await {
|
||||
Ok(cover_art_queue) => {
|
||||
response.message = String::from("Successful");
|
||||
response.data.push(cover_art_queue);
|
||||
(axum::http::StatusCode::OK, axum::Json(response))
|
||||
}
|
||||
Err(err) => {
|
||||
response.message = err.to_string();
|
||||
(axum::http::StatusCode::BAD_REQUEST, axum::Json(response))
|
||||
}
|
||||
},
|
||||
_ => match params.song_queue_id {
|
||||
Some(song_queue_id) => {
|
||||
match super::db::get_coverart_queue_with_song_queue_id(&pool, &song_queue_id)
|
||||
.await
|
||||
{
|
||||
Ok(cover_art_queue) => {
|
||||
response.message = String::from("Successful");
|
||||
response.data.push(cover_art_queue);
|
||||
(axum::http::StatusCode::OK, axum::Json(response))
|
||||
}
|
||||
Err(err) => {
|
||||
response.message = err.to_string();
|
||||
(axum::http::StatusCode::BAD_REQUEST, axum::Json(response))
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
response.message = String::from("No valid id provided");
|
||||
(axum::http::StatusCode::BAD_REQUEST, axum::Json(response))
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn fetch_coverart_with_data(
|
||||
axum::Extension(pool): axum::Extension<sqlx::PgPool>,
|
||||
axum::extract::Path(id): axum::extract::Path<uuid::Uuid>,
|
||||
) -> (axum::http::StatusCode, axum::response::Response) {
|
||||
match super::db::get_coverart_queue_data_with_id(&pool, &id).await {
|
||||
Ok(data) => {
|
||||
let bytes = axum::body::Bytes::from(data);
|
||||
let mut response = bytes.into_response();
|
||||
let headers = response.headers_mut();
|
||||
// TODO: Address this hard coding for the coverart content type
|
||||
headers.insert(axum::http::header::CONTENT_TYPE, "image".parse().unwrap());
|
||||
// TODO: Make the conent disposition more dynamic
|
||||
headers.insert(
|
||||
axum::http::header::CONTENT_DISPOSITION,
|
||||
format!("attachment; filename=\"{id}.jpg\"")
|
||||
.parse()
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
(axum::http::StatusCode::OK, response)
|
||||
}
|
||||
Err(_err) => (
|
||||
axum::http::StatusCode::BAD_REQUEST,
|
||||
axum::response::Response::default(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn create_coverart(
|
||||
axum::Extension(pool): axum::Extension<sqlx::PgPool>,
|
||||
axum::Json(payload): axum::Json<super::request::create_coverart::Request>,
|
||||
) -> (
|
||||
axum::http::StatusCode,
|
||||
axum::Json<super::response::create_coverart::Response>,
|
||||
) {
|
||||
let mut response = super::response::create_coverart::Response::default();
|
||||
let id = payload.coverart_queue_id;
|
||||
|
||||
match super::db::get_coverart_queue_data_with_id(&pool, &id).await {
|
||||
Ok(data) => {
|
||||
let song_id = payload.song_id;
|
||||
match crate::callers::song::song_db::get_song(&pool, &song_id).await {
|
||||
Ok(song) => {
|
||||
let directory = icarus_envy::environment::get_root_directory().await;
|
||||
let dir = std::path::Path::new(&directory);
|
||||
|
||||
// TODO: Make this random and the file extension should not be hard coded
|
||||
let filename = format!("{}-coverart.jpeg", &song.filename[..8]);
|
||||
let save_path = dir.join(&filename);
|
||||
let path = String::from(save_path.to_str().unwrap());
|
||||
let mut coverart =
|
||||
icarus_models::coverart::init::init_coverart_only_path(path);
|
||||
coverart.title = song.album.clone();
|
||||
coverart.data = data;
|
||||
|
||||
let mut file = std::fs::File::create(&save_path).unwrap();
|
||||
file.write_all(&coverart.data).unwrap();
|
||||
|
||||
match super::cov_db::create(&pool, &coverart, &song.id).await {
|
||||
Ok(id) => {
|
||||
// TODO: Populate song_id
|
||||
coverart.id = id;
|
||||
println!("Cover Art created");
|
||||
|
||||
response.message = String::from("Successful");
|
||||
response.data.push(coverart);
|
||||
|
||||
(axum::http::StatusCode::OK, axum::Json(response))
|
||||
}
|
||||
Err(err) => {
|
||||
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) => {
|
||||
response.message = err.to_string();
|
||||
(axum::http::StatusCode::BAD_REQUEST, axum::Json(response))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn wipe_data_from_coverart_queue(
|
||||
axum::Extension(pool): axum::Extension<sqlx::PgPool>,
|
||||
axum::Json(payload): axum::Json<super::request::wipe_data_from_coverart_queue::Request>,
|
||||
) -> (
|
||||
axum::http::StatusCode,
|
||||
axum::Json<super::response::wipe_data_from_coverart_queue::Response>,
|
||||
) {
|
||||
let mut response = super::response::wipe_data_from_coverart_queue::Response::default();
|
||||
let coverart_queue_id = payload.coverart_queue_id;
|
||||
|
||||
match super::db::get_coverart_queue_with_id(&pool, &coverart_queue_id).await {
|
||||
Ok(coverart_queue) => match super::db::wipe_data(&pool, &coverart_queue.id).await {
|
||||
Ok(id) => {
|
||||
response.message = String::from("Success");
|
||||
response.data.push(id);
|
||||
(axum::http::StatusCode::OK, axum::Json(response))
|
||||
}
|
||||
Err(err) => {
|
||||
response.message = err.to_string();
|
||||
(axum::http::StatusCode::BAD_REQUEST, axum::Json(response))
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
response.message = err.to_string();
|
||||
(axum::http::StatusCode::NOT_FOUND, axum::Json(response))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_coverart(
|
||||
axum::Extension(pool): axum::Extension<sqlx::PgPool>,
|
||||
axum::extract::Query(params): axum::extract::Query<super::request::get_coverart::Params>,
|
||||
) -> (
|
||||
axum::http::StatusCode,
|
||||
axum::Json<super::response::get_coverart::Response>,
|
||||
) {
|
||||
let mut response = super::response::get_coverart::Response::default();
|
||||
|
||||
match params.id {
|
||||
Some(id) => match super::cov_db::get_coverart(&pool, &id).await {
|
||||
Ok(coverart) => {
|
||||
response.data.push(coverart);
|
||||
response.message = String::from(super::super::response::SUCCESSFUL);
|
||||
(axum::http::StatusCode::OK, axum::Json(response))
|
||||
}
|
||||
Err(err) => {
|
||||
response.message = err.to_string();
|
||||
(axum::http::StatusCode::BAD_REQUEST, axum::Json(response))
|
||||
}
|
||||
},
|
||||
None => {
|
||||
response.message = String::from("Invalid parameters");
|
||||
(axum::http::StatusCode::BAD_REQUEST, axum::Json(response))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn download_coverart(
|
||||
axum::Extension(pool): axum::Extension<sqlx::PgPool>,
|
||||
axum::extract::Path(id): axum::extract::Path<uuid::Uuid>,
|
||||
) -> (axum::http::StatusCode, axum::response::Response) {
|
||||
match super::cov_db::get_coverart(&pool, &id).await {
|
||||
Ok(coverart) => match coverart.to_data() {
|
||||
Ok(data) => {
|
||||
let bytes = axum::body::Bytes::from(data);
|
||||
let mut response = bytes.into_response();
|
||||
let headers = response.headers_mut();
|
||||
// TODO: Address hard coding
|
||||
headers.insert(
|
||||
axum::http::header::CONTENT_TYPE,
|
||||
"audio/jpg".parse().unwrap(),
|
||||
);
|
||||
headers.insert(
|
||||
axum::http::header::CONTENT_DISPOSITION,
|
||||
format!("attachment; filename=\"{id}.jpg\"")
|
||||
.parse()
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
(axum::http::StatusCode::OK, response)
|
||||
}
|
||||
Err(_err) => (
|
||||
axum::http::StatusCode::NOT_FOUND,
|
||||
axum::response::Response::default(),
|
||||
),
|
||||
},
|
||||
Err(_err) => (
|
||||
axum::http::StatusCode::NOT_FOUND,
|
||||
axum::response::Response::default(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,281 @@
|
||||
// TODO: Explicitly make this module target queueing a song's metadata
|
||||
pub mod request {
|
||||
|
||||
pub mod queue_metadata {
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize, sqlx::FromRow)]
|
||||
pub struct Request {
|
||||
pub song_queue_id: uuid::Uuid,
|
||||
pub album: String,
|
||||
pub album_artist: String,
|
||||
pub artist: String,
|
||||
pub disc: i32,
|
||||
pub disc_count: i32,
|
||||
pub duration: i64,
|
||||
pub genre: String,
|
||||
pub title: String,
|
||||
pub track: i32,
|
||||
pub track_count: i32,
|
||||
pub year: i32,
|
||||
}
|
||||
|
||||
impl Request {
|
||||
pub async fn to_json_value(&self) -> serde_json::Value {
|
||||
serde_json::json!(
|
||||
{
|
||||
"song_queue_id": &self.song_queue_id,
|
||||
"album": &self.album,
|
||||
"album_artist": &self.album_artist,
|
||||
"genre": &self.genre,
|
||||
"year": &self.year,
|
||||
"track_count": &self.track_count,
|
||||
"disc_count": &self.disc_count,
|
||||
"title": &self.title,
|
||||
"artist": &self.artist,
|
||||
"disc": &self.disc,
|
||||
"track": &self.track,
|
||||
"duration": &self.duration,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod fetch_metadata {
|
||||
#[derive(
|
||||
Debug, Default, serde::Deserialize, serde::Serialize, sqlx::FromRow, sqlx::Decode,
|
||||
)]
|
||||
pub struct Params {
|
||||
pub id: Option<uuid::Uuid>,
|
||||
pub song_queue_id: Option<uuid::Uuid>,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod response {
|
||||
|
||||
pub mod queue_metadata {
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Default, Deserialize, Serialize)]
|
||||
pub struct Response {
|
||||
pub message: String,
|
||||
pub data: Vec<uuid::Uuid>,
|
||||
}
|
||||
}
|
||||
|
||||
pub mod fetch_metadata {
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Default, Deserialize, Serialize)]
|
||||
pub struct Response {
|
||||
pub message: String,
|
||||
pub data: Vec<crate::callers::metadata::metadata_queue::MetadataQueue>,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod metadata_queue {
|
||||
use sqlx::Row;
|
||||
|
||||
#[derive(Debug, serde::Serialize, sqlx::FromRow)]
|
||||
pub struct InsertedData {
|
||||
pub id: uuid::Uuid,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize, sqlx::FromRow)]
|
||||
pub struct MetadataQueue {
|
||||
pub id: uuid::Uuid,
|
||||
pub metadata: serde_json::Value,
|
||||
#[serde(with = "time::serde::rfc3339")]
|
||||
pub created_at: time::OffsetDateTime,
|
||||
pub song_queue_id: uuid::Uuid,
|
||||
}
|
||||
|
||||
pub async fn insert(
|
||||
pool: &sqlx::PgPool,
|
||||
metadata: &serde_json::Value,
|
||||
song_queue_id: &uuid::Uuid,
|
||||
) -> Result<uuid::Uuid, sqlx::Error> {
|
||||
let result = sqlx::query(
|
||||
r#"
|
||||
INSERT INTO "metadataQueue" (metadata, song_queue_id) VALUES($1, $2) RETURNING id;
|
||||
"#,
|
||||
)
|
||||
.bind(metadata)
|
||||
.bind(song_queue_id)
|
||||
.fetch_one(pool)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
eprintln!("Error inserting: {e}");
|
||||
});
|
||||
|
||||
match result {
|
||||
Ok(row) => {
|
||||
let id: uuid::Uuid = row
|
||||
.try_get("id")
|
||||
.map_err(|_e| sqlx::Error::RowNotFound)
|
||||
.unwrap();
|
||||
Ok(id)
|
||||
}
|
||||
Err(_err) => Err(sqlx::Error::RowNotFound),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_with_song_queue_id(
|
||||
pool: &sqlx::PgPool,
|
||||
song_queue_id: &uuid::Uuid,
|
||||
) -> Result<MetadataQueue, sqlx::Error> {
|
||||
let result = sqlx::query(
|
||||
r#"
|
||||
SELECT * FROM "metadataQueue" WHERE song_queue_id = $1;
|
||||
"#,
|
||||
)
|
||||
.bind(song_queue_id)
|
||||
.fetch_one(pool)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
eprintln!("Error inserting: {e}");
|
||||
});
|
||||
|
||||
match result {
|
||||
Ok(row) => Ok(MetadataQueue {
|
||||
id: row
|
||||
.try_get("id")
|
||||
.map_err(|_e| sqlx::Error::RowNotFound)
|
||||
.unwrap(),
|
||||
metadata: row
|
||||
.try_get("metadata")
|
||||
.map_err(|_e| sqlx::Error::RowNotFound)
|
||||
.unwrap(),
|
||||
created_at: row
|
||||
.try_get("created_at")
|
||||
.map_err(|_e| sqlx::Error::RowNotFound)
|
||||
.unwrap(),
|
||||
song_queue_id: row
|
||||
.try_get("song_queue_id")
|
||||
.map_err(|_e| sqlx::Error::RowNotFound)
|
||||
.unwrap(),
|
||||
}),
|
||||
Err(_err) => Err(sqlx::Error::RowNotFound),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_with_id(
|
||||
pool: &sqlx::PgPool,
|
||||
id: &uuid::Uuid,
|
||||
) -> Result<MetadataQueue, sqlx::Error> {
|
||||
let result = sqlx::query(
|
||||
r#"
|
||||
SELECT id, metadata, created_at, song_queue_id FROM "metadataQueue" WHERE id = $1;
|
||||
"#,
|
||||
)
|
||||
.bind(id)
|
||||
.fetch_one(pool)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
eprintln!("Error inserting: {e}");
|
||||
});
|
||||
|
||||
match result {
|
||||
Ok(row) => {
|
||||
let data: serde_json::Value = row
|
||||
.try_get("metadata")
|
||||
.map_err(|_e| sqlx::Error::RowNotFound)
|
||||
.unwrap();
|
||||
Ok(MetadataQueue {
|
||||
id: row
|
||||
.try_get("id")
|
||||
.map_err(|_e| sqlx::Error::RowNotFound)
|
||||
.unwrap(),
|
||||
metadata: data,
|
||||
created_at: row
|
||||
.try_get("created_at")
|
||||
.map_err(|_e| sqlx::Error::RowNotFound)
|
||||
.unwrap(),
|
||||
song_queue_id: row
|
||||
.try_get("song_queue_id")
|
||||
.map_err(|_e| sqlx::Error::RowNotFound)
|
||||
.unwrap(),
|
||||
})
|
||||
}
|
||||
Err(_err) => Err(sqlx::Error::RowNotFound),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod endpoint {
|
||||
use axum::{Json, http::StatusCode};
|
||||
|
||||
pub async fn queue_metadata(
|
||||
axum::Extension(pool): axum::Extension<sqlx::PgPool>,
|
||||
Json(payload): Json<super::request::queue_metadata::Request>,
|
||||
) -> (StatusCode, Json<super::response::queue_metadata::Response>) {
|
||||
let mut results: Vec<uuid::Uuid> = Vec::new();
|
||||
let mut response = super::response::queue_metadata::Response::default();
|
||||
let meta = payload.to_json_value().await;
|
||||
match super::metadata_queue::insert(&pool, &meta, &payload.song_queue_id).await {
|
||||
Ok(metadata_queue_id) => {
|
||||
results.push(metadata_queue_id);
|
||||
response.data = results;
|
||||
response.message = if response.data.is_empty() {
|
||||
String::from("Error")
|
||||
} else {
|
||||
String::from("Success")
|
||||
};
|
||||
|
||||
(StatusCode::OK, Json(response))
|
||||
}
|
||||
Err(err) => {
|
||||
response.message = err.to_string();
|
||||
(StatusCode::BAD_REQUEST, Json(response))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn fetch_metadata(
|
||||
axum::Extension(pool): axum::Extension<sqlx::PgPool>,
|
||||
axum::extract::Query(params): axum::extract::Query<super::request::fetch_metadata::Params>,
|
||||
) -> (StatusCode, Json<super::response::fetch_metadata::Response>) {
|
||||
let mut response = super::response::fetch_metadata::Response::default();
|
||||
|
||||
match params.id {
|
||||
Some(id) => {
|
||||
println!("Something works {id}");
|
||||
|
||||
match super::metadata_queue::get_with_id(&pool, &id).await {
|
||||
Ok(item) => {
|
||||
response.message = String::from("Successful");
|
||||
response.data.push(item);
|
||||
(StatusCode::OK, Json(response))
|
||||
}
|
||||
Err(err) => {
|
||||
response.message = err.to_string();
|
||||
(StatusCode::BAD_REQUEST, Json(response))
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => match params.song_queue_id {
|
||||
Some(song_queue_id) => {
|
||||
println!("Song queue Id is probably not nil");
|
||||
match super::metadata_queue::get_with_song_queue_id(&pool, &song_queue_id).await
|
||||
{
|
||||
Ok(item) => {
|
||||
response.message = String::from("Successful");
|
||||
response.data.push(item);
|
||||
(StatusCode::OK, Json(response))
|
||||
}
|
||||
Err(err) => {
|
||||
response.message = err.to_string();
|
||||
(StatusCode::BAD_REQUEST, Json(response))
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
println!("What is going on?");
|
||||
(StatusCode::BAD_REQUEST, Json(response))
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
pub mod coverart;
|
||||
pub mod metadata;
|
||||
pub mod song;
|
||||
|
||||
pub mod endpoints {
|
||||
pub const QUEUESONG: &str = "/api/v2/song/queue";
|
||||
pub const QUEUESONGLINKUSERID: &str = "/api/v2/song/queue/link";
|
||||
pub const QUEUESONGDATA: &str = "/api/v2/song/queue/{id}";
|
||||
pub const QUEUESONGUPDATE: &str = "/api/v2/song/queue/{id}";
|
||||
pub const NEXTQUEUESONG: &str = "/api/v2/song/queue/next";
|
||||
pub const QUEUEMETADATA: &str = "/api/v2/song/metadata/queue";
|
||||
pub const QUEUECOVERART: &str = "/api/v2/coverart/queue";
|
||||
pub const QUEUECOVERARTDATA: &str = "/api/v2/coverart/queue/data/{id}";
|
||||
pub const QUEUECOVERARTLINK: &str = "/api/v2/coverart/queue/link";
|
||||
pub const QUEUESONGDATAWIPE: &str = "/api/v2/song/queue/data/wipe";
|
||||
pub const QUEUECOVERARTDATAWIPE: &str = "/api/v2/coverart/queue/data/wipe";
|
||||
|
||||
pub const CREATESONG: &str = "/api/v2/song";
|
||||
pub const GETSONGS: &str = "/api/v2/song";
|
||||
pub const STREAMSONG: &str = "/api/v2/song/stream/{id}";
|
||||
pub const DOWNLOADSONG: &str = "/api/v2/song/download/{id}";
|
||||
pub const DELETESONG: &str = "/api/v2/song/{id}";
|
||||
pub const CREATECOVERART: &str = "/api/v2/coverart";
|
||||
pub const GETCOVERART: &str = "/api/v2/coverart";
|
||||
pub const DOWNLOADCOVERART: &str = "/api/v2/coverart/download/{id}";
|
||||
}
|
||||
|
||||
pub mod response {
|
||||
pub const SUCCESSFUL: &str = "SUCCESSFUL";
|
||||
}
|
||||
@@ -1,449 +0,0 @@
|
||||
#include "database/AlbumRepository.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
|
||||
database::AlbumRepository::AlbumRepository(const model::BinaryPath& bConf)
|
||||
: BaseRepository(bConf)
|
||||
{ }
|
||||
|
||||
|
||||
std::vector<model::Album> database::AlbumRepository::retrieveRecords()
|
||||
{
|
||||
auto conn = setupMysqlConnection();
|
||||
auto stmt = mysql_stmt_init(conn);
|
||||
|
||||
const std::string query = "SELECT * FROM Album";
|
||||
mysql_stmt_prepare(stmt, query.c_str(), query.size());
|
||||
mysql_stmt_execute(stmt);
|
||||
|
||||
auto albums = parseRecords(stmt);
|
||||
|
||||
mysql_stmt_close(stmt);
|
||||
mysql_close(conn);
|
||||
|
||||
return albums;
|
||||
}
|
||||
|
||||
|
||||
std::pair<model::Album, int> database::AlbumRepository::retrieveRecordWithSongCount(model::Album& album, type::AlbumFilter filter = type::AlbumFilter::id)
|
||||
{
|
||||
std::cout << "retrieving album with song count" << std::endl;
|
||||
std::stringstream qry;
|
||||
auto conn = setupMysqlConnection();
|
||||
auto stmt = mysql_stmt_init(conn);
|
||||
|
||||
MYSQL_BIND params[4];
|
||||
std::memset(params, 0, sizeof(params));
|
||||
|
||||
qry << "SELECT alb.*, COUNT(*) AS SongCount FROM Album alb LEFT JOIN ";
|
||||
qry << "Song sng ON alb.AlbumId=sng.AlbumId WHERE ";
|
||||
|
||||
switch (filter) {
|
||||
case type::AlbumFilter::id:
|
||||
qry << "sng.AlbumId = ?";
|
||||
|
||||
params[0].buffer_type = MYSQL_TYPE_LONG;
|
||||
params[0].buffer = (char*)&album.id;
|
||||
params[0].length = 0;
|
||||
params[0].is_null = 0;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
qry << " GROUP BY alb.AlbumId LIMIT 1";
|
||||
|
||||
const auto query = qry.str();
|
||||
|
||||
auto status = mysql_stmt_prepare(stmt, query.c_str(), query.size());
|
||||
status = mysql_stmt_bind_param(stmt, params);
|
||||
status = mysql_stmt_execute(stmt);
|
||||
|
||||
auto albWSC = parseRecordWithSongCount(stmt);
|
||||
|
||||
mysql_stmt_close(stmt);
|
||||
mysql_close(conn);
|
||||
|
||||
return albWSC;
|
||||
}
|
||||
|
||||
model::Album database::AlbumRepository::retrieveRecord(model::Album& album, type::AlbumFilter filter)
|
||||
{
|
||||
std::cout << "retrieving album record" << std::endl;
|
||||
std::stringstream qry;
|
||||
auto conn = setupMysqlConnection();
|
||||
auto stmt = mysql_stmt_init(conn);
|
||||
|
||||
MYSQL_BIND params[1];
|
||||
memset(params, 0, sizeof(params));
|
||||
|
||||
qry << "SELECT alb.* FROM Album alb WHERE ";
|
||||
|
||||
auto titleLength = album.title.size();
|
||||
switch (filter) {
|
||||
case type::AlbumFilter::id:
|
||||
qry << "alb.AlbumId = ?";
|
||||
|
||||
params[0].buffer_type = MYSQL_TYPE_LONG;
|
||||
params[0].buffer = (char*)&album.id;
|
||||
params[0].length = 0;
|
||||
params[0].is_null = 0;
|
||||
break;
|
||||
case type::AlbumFilter::title:
|
||||
qry << "alb.Title = ?";
|
||||
|
||||
params[0].buffer_type = MYSQL_TYPE_STRING;
|
||||
params[0].buffer = (char*)album.title.c_str();
|
||||
params[0].length = &titleLength;
|
||||
params[0].is_null = 0;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
qry << " LIMIT 1";
|
||||
|
||||
const auto query = qry.str();
|
||||
auto status = mysql_stmt_prepare(stmt, query.c_str(), query.size());
|
||||
status = mysql_stmt_bind_param(stmt, params);
|
||||
status = mysql_stmt_execute(stmt);
|
||||
|
||||
album = parseRecord(stmt);
|
||||
|
||||
mysql_stmt_close(stmt);
|
||||
mysql_close(conn);
|
||||
|
||||
std::cout << "done" << std::endl;
|
||||
|
||||
return album;
|
||||
}
|
||||
|
||||
bool database::AlbumRepository::doesAlbumExists(const model::Album& album, type::AlbumFilter filter)
|
||||
{
|
||||
auto conn = setupMysqlConnection();
|
||||
auto stmt = mysql_stmt_init(conn);
|
||||
|
||||
MYSQL_BIND params[1];
|
||||
memset(params, 0, sizeof(params));
|
||||
|
||||
std::stringstream qry;
|
||||
qry << "SELECT * FROM Album WHERE ";
|
||||
|
||||
auto titleLength = album.title.size();
|
||||
switch (filter) {
|
||||
case type::AlbumFilter::id:
|
||||
qry << "AlbumId = ?";
|
||||
|
||||
params[0].buffer_type = MYSQL_TYPE_LONG;
|
||||
params[0].buffer = (char*)&album.id;
|
||||
params[0].length = 0;
|
||||
params[0].is_null = 0;
|
||||
break;
|
||||
case type::AlbumFilter::title:
|
||||
qry << "Title = ?";
|
||||
|
||||
params[0].buffer_type = MYSQL_TYPE_STRING;
|
||||
params[0].buffer = (char*)album.title.c_str();
|
||||
params[0].length = &titleLength;
|
||||
params[0].is_null = 0;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
qry << " LIMIT 1";
|
||||
|
||||
const auto query = qry.str();
|
||||
auto status = mysql_stmt_prepare(stmt, query.c_str(), query.size());
|
||||
status = mysql_stmt_bind_param(stmt, params);
|
||||
status = mysql_stmt_execute(stmt);
|
||||
|
||||
std::cout << "the query has been performed" << std::endl;
|
||||
|
||||
::mysql_stmt_store_result(stmt);
|
||||
auto rowCount = ::mysql_stmt_num_rows(stmt);
|
||||
|
||||
::mysql_stmt_close(stmt);
|
||||
::mysql_close(conn);
|
||||
|
||||
return (rowCount > 0) ? true : false;
|
||||
}
|
||||
|
||||
void database::AlbumRepository::saveAlbum(const model::Album& album)
|
||||
{
|
||||
std::cout << "beginning to insert album record" << std::endl;
|
||||
|
||||
auto conn = setupMysqlConnection();
|
||||
MYSQL_STMT *stmt = mysql_stmt_init(conn);
|
||||
|
||||
const std::string query = "INSERT INTO Album(Title, Year) VALUES(?, ?)";
|
||||
|
||||
auto status = mysql_stmt_prepare(stmt, query.c_str(), query.size());
|
||||
|
||||
MYSQL_BIND params[2];
|
||||
memset(params, 0, sizeof(params));
|
||||
|
||||
params[0].buffer_type = MYSQL_TYPE_STRING;
|
||||
params[0].buffer = (char*)album.title.c_str();
|
||||
auto titleLength = album.title.size();
|
||||
params[0].length= &titleLength;
|
||||
params[0].is_null = 0;
|
||||
|
||||
params[1].buffer_type = MYSQL_TYPE_LONG;
|
||||
params[1].buffer = (char*)&album.year;
|
||||
params[1].length = 0;
|
||||
params[1].is_null = 0;
|
||||
|
||||
status = mysql_stmt_bind_param(stmt, params);
|
||||
status = mysql_stmt_execute(stmt);
|
||||
|
||||
mysql_stmt_close(stmt);
|
||||
mysql_close(conn);
|
||||
|
||||
std::cout << "done inserting album record" << std::endl;
|
||||
}
|
||||
|
||||
void database::AlbumRepository::deleteAlbum(const model::Album& album, type::AlbumFilter filter = type::AlbumFilter::id)
|
||||
{
|
||||
std::cout << "deleting album record" << std::endl;
|
||||
|
||||
std::stringstream qry;
|
||||
auto conn = setupMysqlConnection();
|
||||
auto stmt = mysql_stmt_init(conn);
|
||||
|
||||
MYSQL_BIND params[1];
|
||||
std::memset(params, 0, sizeof(params));
|
||||
|
||||
qry << "DELETE FROM Album WHERE ";
|
||||
|
||||
switch (filter) {
|
||||
case type::AlbumFilter::id:
|
||||
qry << "AlbumId = ?";
|
||||
|
||||
params[0].buffer_type = MYSQL_TYPE_LONG;
|
||||
params[0].buffer = (char*)&album.id;
|
||||
params[0].length = 0;
|
||||
params[0].is_null = 0;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
const auto query = qry.str();
|
||||
|
||||
auto status = mysql_stmt_prepare(stmt, query.c_str(), query.size());
|
||||
status = mysql_stmt_bind_param(stmt, params);
|
||||
status = mysql_stmt_execute(stmt);
|
||||
|
||||
std::cout << "execute delete query" << std::endl;
|
||||
}
|
||||
|
||||
|
||||
std::vector<model::Album> database::AlbumRepository::parseRecords(MYSQL_STMT* stmt)
|
||||
{
|
||||
std::cout << "parsing album record" << std::endl;
|
||||
mysql_stmt_store_result(stmt);
|
||||
|
||||
std::vector<model::Album> albums;
|
||||
albums.reserve(mysql_stmt_num_rows(stmt));
|
||||
|
||||
const auto valAmt = 3;
|
||||
unsigned long len[valAmt];
|
||||
my_bool nullRes[valAmt];
|
||||
|
||||
for (auto status = 0; status == 0; status = mysql_stmt_next_result(stmt)) {
|
||||
if (mysql_stmt_field_count(stmt) > 0) {
|
||||
model::Album alb;
|
||||
auto res = mysql_stmt_result_metadata(stmt);
|
||||
auto fields = mysql_fetch_fields(res);
|
||||
const auto strLen = 1024;
|
||||
|
||||
MYSQL_BIND val[valAmt];
|
||||
memset(val, 0, sizeof(val));
|
||||
|
||||
char title[strLen];
|
||||
|
||||
val[0].buffer_type = MYSQL_TYPE_LONG;
|
||||
val[0].buffer = (char*)&alb.id;
|
||||
val[0].length = &len[0];
|
||||
val[0].is_null = &nullRes[0];
|
||||
|
||||
val[1].buffer_type = MYSQL_TYPE_STRING;
|
||||
val[1].buffer = (char*)title;
|
||||
val[1].buffer_length = strLen;
|
||||
val[1].length = &len[1];
|
||||
val[1].is_null = &nullRes[1];
|
||||
|
||||
val[2].buffer_type = MYSQL_TYPE_LONG;
|
||||
val[2].buffer = (char*)&alb.year;
|
||||
val[2].length = &len[2];
|
||||
val[2].is_null = &nullRes[2];
|
||||
|
||||
status = mysql_stmt_bind_result(stmt, val);
|
||||
|
||||
while (true) {
|
||||
std::cout << "fetching statement result" << std::endl;
|
||||
status = mysql_stmt_fetch(stmt);
|
||||
|
||||
if (status == 1 || status == MYSQL_NO_DATA) {
|
||||
break;
|
||||
}
|
||||
|
||||
alb.title = title;
|
||||
albums.push_back(std::move(alb));
|
||||
}
|
||||
}
|
||||
std::cout << "fetching next result" << std::endl;
|
||||
}
|
||||
|
||||
return albums;
|
||||
}
|
||||
|
||||
// TODO: check to see if this is not used, if not then remove it
|
||||
model::Album database::AlbumRepository::parseRecord(MYSQL_RES* results)
|
||||
{
|
||||
std::cout << "parsing album record" << std::endl;
|
||||
model::Album album;
|
||||
|
||||
auto fieldNum = mysql_num_fields(results);
|
||||
auto row = mysql_fetch_row(results);
|
||||
|
||||
for (auto i = 0; i != fieldNum; ++i) {
|
||||
const std::string field(mysql_fetch_field(results)->name);
|
||||
|
||||
if (field.compare("AlbumId") == 0) {
|
||||
album.id = std::stoi(row[i]);
|
||||
}
|
||||
if (field.compare("Title") == 0) {
|
||||
album.title = row[i];
|
||||
}
|
||||
if (field.compare("Year") == 0) {
|
||||
album.year = std::stoi(row[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return album;
|
||||
}
|
||||
|
||||
std::pair<model::Album, int> database::AlbumRepository::parseRecordWithSongCount(MYSQL_STMT *stmt)
|
||||
{
|
||||
std::cout << "parsing album record with song count" << std::endl;
|
||||
mysql_stmt_store_result(stmt);
|
||||
auto rowCount = mysql_stmt_num_rows(stmt);
|
||||
|
||||
model::Album album;
|
||||
int songCount = 0;
|
||||
|
||||
if (rowCount == 0) {
|
||||
std::cout << "no results" << std::endl;
|
||||
return std::make_pair(album, songCount);
|
||||
}
|
||||
|
||||
if (mysql_stmt_field_count(stmt) == 0) {
|
||||
std::cout << "field count is 0, must be an incorrect query" << std::endl;
|
||||
return std::make_pair(model::Album(), 0);
|
||||
}
|
||||
|
||||
auto res = mysql_stmt_result_metadata(stmt);
|
||||
constexpr auto valAmt = 4;
|
||||
constexpr auto strLen = 1024;
|
||||
unsigned long len[valAmt];
|
||||
my_bool nullRes[valAmt];
|
||||
|
||||
MYSQL_BIND val[valAmt];
|
||||
std::memset(val, 0, sizeof(val));
|
||||
|
||||
char title[strLen];
|
||||
|
||||
val[0].buffer_type = MYSQL_TYPE_LONG;
|
||||
val[0].buffer = (char*)&album.id;
|
||||
val[0].length = &len[0];
|
||||
val[0].is_null = &nullRes[0];
|
||||
|
||||
val[1].buffer_type = MYSQL_TYPE_STRING;
|
||||
val[1].buffer = (char*)title;
|
||||
val[1].buffer_length = strLen;
|
||||
val[1].length = &len[1];
|
||||
val[1].is_null = &nullRes[1];
|
||||
|
||||
val[2].buffer_type = MYSQL_TYPE_LONG;
|
||||
val[2].buffer = (char*)&album.year;
|
||||
val[2].length = &len[2];
|
||||
val[2].is_null = &nullRes[2];
|
||||
|
||||
val[3].buffer_type = MYSQL_TYPE_LONG;
|
||||
val[3].buffer = (char*)&songCount;
|
||||
val[3].length = &len[3];
|
||||
val[3].is_null = &nullRes[3];
|
||||
|
||||
auto status = mysql_stmt_bind_result(stmt, val);
|
||||
status = mysql_stmt_fetch(stmt);
|
||||
|
||||
album.title = std::move(title);
|
||||
|
||||
std::cout << "done parsing album record with song count" << std::endl;
|
||||
|
||||
auto albWSC = std::make_pair(album, songCount);
|
||||
|
||||
return albWSC;
|
||||
}
|
||||
|
||||
model::Album database::AlbumRepository::parseRecord(MYSQL_STMT *stmt)
|
||||
{
|
||||
std::cout << "parsing album record" << std::endl;
|
||||
mysql_stmt_store_result(stmt);
|
||||
auto rows = mysql_stmt_num_rows(stmt);
|
||||
|
||||
model::Album album;
|
||||
auto status = 0;
|
||||
auto time = 0;
|
||||
auto valAmt = 3;
|
||||
unsigned long len[valAmt];
|
||||
my_bool nullRes[valAmt];
|
||||
|
||||
while (status == 0) {
|
||||
if (mysql_stmt_field_count(stmt) > 0) {
|
||||
auto res = mysql_stmt_result_metadata(stmt);
|
||||
auto fields = mysql_fetch_fields(res);
|
||||
auto strLen = 1024;
|
||||
|
||||
MYSQL_BIND val[valAmt];
|
||||
memset(val, 0, sizeof(val));
|
||||
|
||||
char title[strLen];
|
||||
|
||||
val[0].buffer_type = MYSQL_TYPE_LONG;
|
||||
val[0].buffer = (char*)&album.id;
|
||||
val[0].length = &len[0];
|
||||
val[0].is_null = &nullRes[0];
|
||||
|
||||
val[1].buffer_type = MYSQL_TYPE_STRING;
|
||||
val[1].buffer = (char*)title;
|
||||
val[1].buffer_length = strLen;
|
||||
val[1].length = &len[1];
|
||||
val[1].is_null = &nullRes[1];
|
||||
|
||||
val[2].buffer_type = MYSQL_TYPE_LONG;
|
||||
val[2].buffer = (char*)&album.year;
|
||||
val[2].length = &len[2];
|
||||
val[2].is_null = &nullRes[2];
|
||||
|
||||
status = mysql_stmt_bind_result(stmt, val);
|
||||
mysql_stmt_store_result(stmt);
|
||||
|
||||
status = mysql_stmt_fetch(stmt);
|
||||
|
||||
album.title = title;
|
||||
}
|
||||
|
||||
status = mysql_stmt_next_result(stmt);
|
||||
}
|
||||
|
||||
std::cout << "done parsing album record" << std::endl;
|
||||
|
||||
return album;
|
||||
}
|
||||
@@ -1,415 +0,0 @@
|
||||
#include "database/ArtistRepository.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
|
||||
database::ArtistRepository::ArtistRepository(const model::BinaryPath& binConf)
|
||||
: BaseRepository(binConf)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
std::vector<model::Artist> database::ArtistRepository::retrieveRecords()
|
||||
{
|
||||
auto conn = setupMysqlConnection();
|
||||
auto stmt = mysql_stmt_init(conn);
|
||||
const std::string query = "SELECT * FROM Artist";
|
||||
|
||||
mysql_stmt_prepare(stmt, query.c_str(), query.size());
|
||||
mysql_stmt_execute(stmt);
|
||||
|
||||
auto artists = parseRecords(stmt);
|
||||
|
||||
mysql_stmt_close(stmt);
|
||||
mysql_close(conn);
|
||||
|
||||
return artists;
|
||||
}
|
||||
|
||||
std::pair<model::Artist, int> database::ArtistRepository::retrieveRecordWithSongCount(model::Artist& artist, type::ArtistFilter filter = type::ArtistFilter::id)
|
||||
{
|
||||
std::cout << "retrieving artist record with song count" << std::endl;
|
||||
std::stringstream qry;
|
||||
auto conn = setupMysqlConnection();
|
||||
auto stmt = mysql_stmt_init(conn);
|
||||
|
||||
qry << "SELECT art.*, COUNT(*) AS SongCount FROM Artist art LEFT JOIN ";
|
||||
qry << "Song sng ON art.ArtistId=sng.ArtistId WHERE ";
|
||||
|
||||
MYSQL_BIND params[1];
|
||||
std::memset(params, 0, sizeof(params));
|
||||
|
||||
switch (filter) {
|
||||
case type::ArtistFilter::id:
|
||||
qry << "sng.ArtistId = ?";
|
||||
|
||||
params[0].buffer_type = MYSQL_TYPE_LONG;
|
||||
params[0].buffer = (char*)&artist.id;
|
||||
params[0].length = 0;
|
||||
params[0].is_null = 0;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
qry << " GROUP BY art.ArtistId LIMIT 1";
|
||||
|
||||
const auto query = qry.str();
|
||||
auto status = mysql_stmt_prepare(stmt, query.c_str(), query.size());
|
||||
status = mysql_stmt_bind_param(stmt, params);
|
||||
status = mysql_stmt_execute(stmt);
|
||||
|
||||
std::cout << "Artist record with song count query ";
|
||||
std::cout << "has been performed" << std::endl;
|
||||
|
||||
auto artWSC = parseRecordWithSongCount(stmt);
|
||||
|
||||
mysql_stmt_close(stmt);
|
||||
mysql_close(conn);
|
||||
|
||||
return artWSC;
|
||||
}
|
||||
|
||||
model::Artist database::ArtistRepository::retrieveRecord(model::Artist& artist, type::ArtistFilter filter)
|
||||
{
|
||||
std::cout << "retrieving artist record" << std::endl;
|
||||
std::stringstream qry;
|
||||
auto conn = setupMysqlConnection();
|
||||
auto stmt = mysql_stmt_init(conn);
|
||||
qry << "SELECT art.* FROM Artist art WHERE ";
|
||||
|
||||
MYSQL_BIND params[1];
|
||||
memset(params, 0, sizeof(params));
|
||||
|
||||
auto artistLength = artist.artist.size();
|
||||
switch (filter) {
|
||||
case type::ArtistFilter::id:
|
||||
qry << "art.ArtistId = ?";
|
||||
|
||||
params[0].buffer_type = MYSQL_TYPE_LONG;
|
||||
params[0].buffer = (char*)&artist.id;
|
||||
params[0].length = 0;
|
||||
params[0].is_null = 0;
|
||||
break;
|
||||
case type::ArtistFilter::artist:
|
||||
qry << "art.Artist = ?";
|
||||
|
||||
params[0].buffer_type = MYSQL_TYPE_STRING;
|
||||
params[0].buffer = (char*)artist.artist.c_str();
|
||||
params[0].length = &artistLength;
|
||||
params[0].is_null = 0;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
qry << " LIMIT 1";
|
||||
|
||||
const auto query = qry.str();
|
||||
auto status = mysql_stmt_prepare(stmt, query.c_str(), query.size());
|
||||
status = mysql_stmt_bind_param(stmt, params);
|
||||
status = mysql_stmt_execute(stmt);
|
||||
|
||||
artist = parseRecord(stmt);
|
||||
|
||||
mysql_stmt_close(stmt);
|
||||
mysql_close(conn);
|
||||
|
||||
std::cout << "retrieved record" << std::endl;
|
||||
|
||||
return artist;
|
||||
}
|
||||
|
||||
bool database::ArtistRepository::doesArtistExist(const model::Artist& artist, type::ArtistFilter filter)
|
||||
{
|
||||
auto conn = setupMysqlConnection();
|
||||
auto stmt = mysql_stmt_init(conn);
|
||||
|
||||
MYSQL_BIND params[1];
|
||||
memset(params, 0, sizeof(params));
|
||||
|
||||
std::stringstream qry;
|
||||
qry << "SELECT * FROM Artist WHERE ";
|
||||
|
||||
auto artistLength = artist.artist.size();
|
||||
switch (filter) {
|
||||
case type::ArtistFilter::id:
|
||||
qry << "ArtistId = ?";
|
||||
|
||||
params[0].buffer_type = MYSQL_TYPE_LONG;
|
||||
params[0].buffer = (char*)&artist.id;
|
||||
params[0].length = 0;
|
||||
params[0].is_null = 0;
|
||||
break;
|
||||
case type::ArtistFilter::artist:
|
||||
qry << "Artist = ?";
|
||||
|
||||
params[0].buffer_type = MYSQL_TYPE_STRING;
|
||||
params[0].buffer = (char*)artist.artist.c_str();
|
||||
params[0].length = &artistLength;
|
||||
params[0].is_null = 0;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
qry << " LIMIT 1";
|
||||
|
||||
const auto query = qry.str();
|
||||
auto status = mysql_stmt_prepare(stmt, query.c_str(), query.size());
|
||||
status = mysql_stmt_bind_param(stmt, params);
|
||||
status = mysql_stmt_execute(stmt);
|
||||
|
||||
std::cout << "the query has been performed" << std::endl;
|
||||
|
||||
mysql_stmt_store_result(stmt);
|
||||
auto rowCount = mysql_stmt_num_rows(stmt);
|
||||
|
||||
mysql_stmt_close(stmt);
|
||||
mysql_close(conn);
|
||||
|
||||
return (rowCount > 0) ? true : false;
|
||||
}
|
||||
|
||||
void database::ArtistRepository::saveRecord(const model::Artist& artist)
|
||||
{
|
||||
std::cout << "inserting artist record" << std::endl;
|
||||
|
||||
auto conn = setupMysqlConnection();
|
||||
MYSQL_STMT *stmt = mysql_stmt_init(conn);
|
||||
|
||||
const std::string query = "INSERT INTO Artist(Artist) VALUES(?)";
|
||||
|
||||
auto status = mysql_stmt_prepare(stmt, query.c_str(), query.size());
|
||||
|
||||
MYSQL_BIND params[1];
|
||||
memset(params, 0, sizeof(params));
|
||||
|
||||
params[0].buffer_type = MYSQL_TYPE_STRING;
|
||||
params[0].buffer = (char*)artist.artist.c_str();
|
||||
auto artistLength = artist.artist.size();
|
||||
params[0].length = &artistLength;
|
||||
params[0].is_null = 0;
|
||||
|
||||
status = mysql_stmt_bind_param(stmt, params);
|
||||
status = mysql_stmt_execute(stmt);
|
||||
|
||||
mysql_stmt_close(stmt);
|
||||
mysql_close(conn);
|
||||
|
||||
std::cout<< "inserted artist record" << std::endl;
|
||||
}
|
||||
|
||||
void database::ArtistRepository::deleteArtist(const model::Artist& artist, type::ArtistFilter filter = type::ArtistFilter::id) {
|
||||
// TODO: implement this
|
||||
std::cout << "delete Artist record" << std::endl;
|
||||
std::stringstream qry;
|
||||
auto conn = setupMysqlConnection();
|
||||
auto stmt = mysql_stmt_init(conn);
|
||||
|
||||
qry << "DELETE FROM Artist WHERE ";
|
||||
|
||||
MYSQL_BIND params[1];
|
||||
std::memset(params, 0, sizeof(params));
|
||||
|
||||
switch (filter) {
|
||||
case type::ArtistFilter::id:
|
||||
qry << "ArtistId = ?";
|
||||
|
||||
params[0].buffer_type = MYSQL_TYPE_LONG;
|
||||
params[0].buffer = (char*)&artist.id;
|
||||
params[0].length = 0;
|
||||
params[0].is_null = 0;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
const auto query = qry.str();
|
||||
|
||||
auto status = mysql_stmt_prepare(stmt, query.c_str(), query.size());
|
||||
status = mysql_stmt_bind_param(stmt, params);
|
||||
status = mysql_stmt_execute(stmt);
|
||||
|
||||
mysql_stmt_close(stmt);
|
||||
mysql_close(conn);
|
||||
|
||||
std::cout << "deleted artist record" << std::endl;
|
||||
}
|
||||
|
||||
std::vector<model::Artist> database::ArtistRepository::parseRecords(MYSQL_STMT *stmt)
|
||||
{
|
||||
mysql_stmt_store_result(stmt);
|
||||
|
||||
std::vector<model::Artist> artists;
|
||||
artists.reserve(mysql_stmt_num_rows(stmt));
|
||||
|
||||
if (mysql_stmt_field_count(stmt) == 0) {
|
||||
std::cout << "field count is 0" << std::endl;
|
||||
return artists;
|
||||
}
|
||||
|
||||
model::Artist art;
|
||||
const auto valAmt = 2;
|
||||
unsigned long len[valAmt];
|
||||
my_bool nullRes[valAmt];
|
||||
|
||||
auto res = mysql_stmt_result_metadata(stmt);
|
||||
auto fields = mysql_fetch_fields(res);
|
||||
const auto strLen = 1024;
|
||||
|
||||
char artist[strLen];
|
||||
|
||||
MYSQL_BIND val[valAmt];
|
||||
memset(val, 0, sizeof(val));
|
||||
|
||||
val[0].buffer_type = MYSQL_TYPE_LONG;
|
||||
val[0].buffer = (char*)&art.id;
|
||||
val[0].length = &len[0];
|
||||
val[0].is_null = &nullRes[0];
|
||||
|
||||
val[1].buffer_type = MYSQL_TYPE_STRING;
|
||||
val[1].buffer = (char*)artist;
|
||||
val[1].buffer_length = strLen;
|
||||
val[1].length = &len[1];
|
||||
val[1].is_null = &nullRes[1];
|
||||
|
||||
for (auto status = mysql_stmt_bind_result(stmt, val); status == 0; ) {
|
||||
std::cout << "fetching statement result" << std::endl;
|
||||
status = mysql_stmt_fetch(stmt);
|
||||
|
||||
if (status == 0) {
|
||||
art.artist = artist;
|
||||
artists.push_back(std::move(art));
|
||||
}
|
||||
}
|
||||
|
||||
return artists;
|
||||
}
|
||||
|
||||
|
||||
model::Artist database::ArtistRepository::parseRecord(MYSQL_RES* results)
|
||||
{
|
||||
std::cout << "parsing artist record" << std::endl;
|
||||
model::Artist artist;
|
||||
|
||||
auto fieldNum = mysql_num_fields(results);
|
||||
auto row = mysql_fetch_row(results);
|
||||
|
||||
for (auto i = 0; i != fieldNum; ++i) {
|
||||
const std::string field(mysql_fetch_field(results)->name);
|
||||
|
||||
if (field.compare("ArtistId") == 0) {
|
||||
artist.id = std::stoi(row[i]);
|
||||
}
|
||||
if (field.compare("Artist") == 0) {
|
||||
artist.artist = row[i];
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "parsed artist record" << std::endl;
|
||||
|
||||
return artist;
|
||||
}
|
||||
|
||||
std::pair<model::Artist, int> database::ArtistRepository::parseRecordWithSongCount(MYSQL_STMT *stmt)
|
||||
{
|
||||
std::cout << "parsing artist record with song count" << std::endl;
|
||||
mysql_stmt_store_result(stmt);
|
||||
|
||||
const auto strLen = 1024;
|
||||
const auto valAmt = 3;
|
||||
unsigned long len[valAmt];
|
||||
my_bool nullRes[valAmt];
|
||||
|
||||
model::Artist artist;
|
||||
int songCount = 0;
|
||||
|
||||
if (mysql_stmt_num_rows(stmt) == 0) {
|
||||
std::cout << "no results" << std::endl;
|
||||
return std::make_pair(artist, songCount);
|
||||
}
|
||||
|
||||
MYSQL_BIND params[valAmt];
|
||||
std::memset(params, 0, sizeof(params));
|
||||
|
||||
char art[strLen];
|
||||
|
||||
params[0].buffer_type = MYSQL_TYPE_LONG;
|
||||
params[0].buffer = (char*)&artist.id;
|
||||
params[0].length = &len[0];
|
||||
params[0].is_null = &nullRes[0];
|
||||
|
||||
params[1].buffer_type = MYSQL_TYPE_STRING;
|
||||
params[1].buffer = (char*)art;
|
||||
params[1].buffer_length = strLen;
|
||||
params[1].length = &len[1];
|
||||
params[1].is_null = &nullRes[1];
|
||||
|
||||
params[2].buffer_type = MYSQL_TYPE_LONG;
|
||||
params[2].buffer = (char*)&songCount;
|
||||
params[2].length = &len[2];
|
||||
params[2].is_null = &nullRes[2];
|
||||
|
||||
mysql_stmt_bind_result(stmt, params);
|
||||
mysql_stmt_fetch(stmt);
|
||||
|
||||
artist.artist = std::move(art);
|
||||
|
||||
std::cout << "parsed artist record with song count" << std::endl;
|
||||
|
||||
return std::make_pair(artist, songCount);
|
||||
}
|
||||
|
||||
model::Artist database::ArtistRepository::parseRecord(MYSQL_STMT *stmt)
|
||||
{
|
||||
std::cout << "parsing artist record" << std::endl;
|
||||
mysql_stmt_store_result(stmt);
|
||||
|
||||
model::Artist art;
|
||||
auto status = 0;
|
||||
auto time = 0;
|
||||
auto valAmt = 2;
|
||||
unsigned long len[valAmt];
|
||||
my_bool nullRes[valAmt];
|
||||
|
||||
while (status == 0) {
|
||||
if (mysql_stmt_field_count(stmt) > 0) {
|
||||
auto res = mysql_stmt_result_metadata(stmt);
|
||||
auto fields = mysql_fetch_fields(res);
|
||||
auto strLen = 1024;
|
||||
|
||||
MYSQL_BIND val[valAmt];
|
||||
memset(val, 0, sizeof(val));
|
||||
|
||||
char artist[strLen];
|
||||
|
||||
val[0].buffer_type = MYSQL_TYPE_LONG;
|
||||
val[0].buffer = (char*)&art.id;
|
||||
val[0].length = &len[0];
|
||||
val[0].is_null = &nullRes[0];
|
||||
|
||||
val[1].buffer_type = MYSQL_TYPE_STRING;
|
||||
val[1].buffer = (char*)&artist;
|
||||
val[1].buffer_length = strLen;
|
||||
val[1].length = &len[1];
|
||||
val[1].is_null = &nullRes[1];
|
||||
|
||||
status = mysql_stmt_bind_result(stmt, val);
|
||||
mysql_stmt_store_result(stmt);
|
||||
|
||||
status = mysql_stmt_fetch(stmt);
|
||||
|
||||
art.artist = artist;
|
||||
}
|
||||
|
||||
status = mysql_stmt_next_result(stmt);
|
||||
}
|
||||
|
||||
std::cout << "done parsing artist record" << std::endl;
|
||||
|
||||
return art;
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
#include "database/BaseRepository.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include "manager/DirectoryManager.h"
|
||||
|
||||
database::BaseRepository::BaseRepository()
|
||||
{ }
|
||||
|
||||
database::BaseRepository::BaseRepository(const std::string& path) : path(path)
|
||||
{
|
||||
intitalizeDetails();
|
||||
}
|
||||
|
||||
database::BaseRepository::BaseRepository(const model::BinaryPath& bConf)
|
||||
{
|
||||
initializeDetails(bConf);
|
||||
}
|
||||
|
||||
|
||||
bool database::BaseRepository::testConnection()
|
||||
{
|
||||
auto conn = mysql_init(nullptr);
|
||||
if (!mysql_real_connect(conn, details.server.c_str(), details.username.c_str(),
|
||||
details.password.c_str(), details.database.c_str(), 0,
|
||||
nullptr, 0)) {
|
||||
std::cout << "failed to connect to the database" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
mysql_close(conn);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
MYSQL* database::BaseRepository::setupMysqlConnection()
|
||||
{
|
||||
MYSQL *conn = mysql_init(nullptr);
|
||||
|
||||
if (!mysql_real_connect(conn, details.server.c_str(), details.username.c_str(),
|
||||
details.password.c_str(), details.database.c_str(), 0, nullptr, 0)) {
|
||||
std::cout << "connection error" << std::endl;
|
||||
}
|
||||
|
||||
return conn;
|
||||
}
|
||||
|
||||
MYSQL* database::BaseRepository::setupMysqlConnection(model::DatabaseConnection details)
|
||||
{
|
||||
MYSQL *connection = mysql_init(NULL);
|
||||
|
||||
// connect to the database with the details attached.
|
||||
if (!mysql_real_connect(connection,details.server.c_str(), details.username.c_str(), details.password.c_str(), details.database.c_str(), 0, NULL, 0)) {
|
||||
printf("Conection error : %s\n", mysql_error(connection));
|
||||
exit(1);
|
||||
}
|
||||
return connection;
|
||||
}
|
||||
|
||||
|
||||
MYSQL_RES* database::BaseRepository::performMysqlQuery(MYSQL *conn, const std::string& query)
|
||||
{
|
||||
// send the query to the database
|
||||
if (mysql_query(conn, query.c_str()))
|
||||
{
|
||||
printf("MySQL query error : %s\n", mysql_error(conn));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
return mysql_use_result(conn);
|
||||
}
|
||||
|
||||
|
||||
void database::BaseRepository::intitalizeDetails()
|
||||
{
|
||||
auto databaseConfig = manager::DirectoryManager::databaseConfigContent(path);
|
||||
|
||||
details.database = databaseConfig["database"].get<std::string>();
|
||||
details.password = databaseConfig["password"].get<std::string>();
|
||||
details.server = databaseConfig["server"].get<std::string>();
|
||||
details.username = databaseConfig["username"].get<std::string>();
|
||||
}
|
||||
void database::BaseRepository::initializeDetails(const model::BinaryPath& bConf)
|
||||
{
|
||||
auto databaseConfig = manager::DirectoryManager::databaseConfigContent(bConf);
|
||||
|
||||
details.database = databaseConfig["database"].get<std::string>();
|
||||
details.server = databaseConfig["server"].get<std::string>();
|
||||
details.username = databaseConfig["username"].get<std::string>();
|
||||
details.password = databaseConfig["password"].get<std::string>();
|
||||
|
||||
std::cout << "retrieved database details" << std::endl;
|
||||
}
|
||||