Compare commits

..

133 Commits

Author SHA1 Message Date
KD c7230d3b32 Download coverart endpoint (#166)
* Added download coverart endpoint

* Endpoint is now available

* Added test

* Code formatting

* Version bump
2025-07-28 17:05:42 -04:00
KD 703fc49f32 Downloading song endpoint (#165)
* Added initial code for downloading song endpoint

* Finished download song endpoint

* Making download endpoint available

* Added test

* Code formatting

* Version bump
2025-07-28 16:40:14 -04:00
KD e98e055de0 Stream song (#164)
* Added crates and dependency features

* Added endpoint to stream songs

* Changed name of function

* Song stream endpoint is now available

* Endpoint is now working

* Added test

* Warning fixes

* Code formatting

* Added TODO for later

* Test fix

* Version bump
2025-07-27 14:20:42 -04:00
KD a8603361c0 Get coverart (#163)
* Added initial code to get coverart endpoint

* Test migration cleanup

* Updated icarus_models

* Made endpoint evailable

* Added test

* Version bump

* Code formatting
2025-07-26 14:12:03 -04:00
KD dbda9a3897 Get songs endpoint (#162)
* Added test_migrations directory

* Added skeleton endpoint to get songs

* Code formatting

* Added more code

* Added TODO

* Code formatting

* Updated album in album json file

* Changing directory name

* Test refactoring

* Forgot to include this

* Added test for getting songs and added function for test_migrations

* Adding test migrations

* Removing placeholder

* Renamed

* Renamed

* Fixed what caused test failure

* Created migration with cli command

Creating test_migrations/20250725213448_migration_name.sql

* Migration changes

* Removing migration

* More migration changes

* Made endpoint available

* Migration changes

* Got the test to work

* Cleaned up test

* Code formatting

* More cleanup

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

* Refactor test

* Added CoverArt file for test files

* Added album json file for testing

* Modifying test json file

* Modifying test file for consistency

* Test refactoring

* Test file changes

* Removing commented code

* Removing files used for testing

* Removing placeholder

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

* Temp change

* File extension change

* Syntax change

* Removing that temp change
2025-07-22 21:59:20 -04:00
KD 34e292bea9 Link user id to song queue (#159)
* Added initial code for endpoint to link user id with song queue #156

* Updated songQueue to have a user_id field #156

* Added function to link user_id with songQueue record #156

* Added useful constant #156

* Finished adding code #156

Need to make the endpoint available and then write a test. Might have to modify existing tests

* Made link user id endpoint available #156

* Added TODOs for later

* Changing location of test code

* Code formatting

* Added test #156

* Formatted code

* Added user_id to SongQueue and added more related code

* Code formatting

* Fixing possible nil user_id field in SongQueue when calling a function

* Fixed another issue

* Made changes to function to also return user_id

* Making use of user_id in test requests

* Code formatting

* Updated workflow

* Forgot to include port

* Docker changes for port change

* Reverting port changes

* Cleaning up Dockerfile

* Version bump
2025-07-22 21:36:29 -04:00
KD c9bcea5f98 Version change (#158) 2025-07-15 16:15:51 -04:00
KD 6c9e4809a9 Fixing bug where message in response was unpopulated on successful requests (#157) 2025-07-15 15:10:51 -04:00
KD 4c5bd1ccde Updated icarus_models (#155) 2025-07-14 20:09:43 -04:00
KD 5a2639cb59 Added error checking for file creation in create metadata endpoint (#154)
* Added error checking for file creation in create metadata endpoint

* Code formatting

* Fixed create metadata endoint

* Added missing message in the response

* Cleanup

* Warning fixes

* Code formatting
2025-07-14 19:42:07 -04:00
KD fd9df41f51 Updated rust version in docker (#152) 2025-07-13 18:54:47 -04:00
KD 7fdea3491f Upgrading postgresql to 17.5 (#151)
Closes #150
2025-07-12 15:59:23 -04:00
KD 3cde57dca8 Refactoring (#149)
* Warning fixes

* More warning fixes

* Cargo.toml cleanup
2025-06-29 17:33:45 -04:00
KD 8c93429647 Update dependencies (#148)
* Updating Cargo.lock

* Updating axum

* Updated tokio, tokio-util, and tower-http

* Updated uuid and sqlx

* Updated icarus_models, icarus_envy, and icarus_meta

* Updated tempfile
2025-06-29 17:15:20 -04:00
KD 5721c387ea Rust std change (#147)
* Targeting rust 1.88

* Updating workflow
2025-06-29 17:03:59 -04:00
KD a624702813 Update song queue endpoint fix (#146)
* Bug fix that was causing errors

* Code formatting

* Removed commented code
2025-06-28 19:06:41 -04:00
KD 9c67bfd8a8 Update song queue changes (#145)
* Updated gitignore to disregard some .env files

.env.docker and .env.local. Primarily used for switching between local and docker development

* Changed PATCH /api/v2/song/queue/{id} endpoint

Modified the response body. Will just return a list of song queue id

* Adding some helpful info for the response body

* Tidying up and making some changes

* Code formatting
2025-06-28 16:45:25 -04:00
KD fbec3305b1 Reorganizing metadata queue code (#144)
* Reorganizing metadata queue code

* Function name change

* Test payload changes

* function parameter name change
2025-06-18 19:47:41 -04:00
KD fa792be9cc Fixed endpoint for getting coverart queue data (#143)
* Fixed endpoint for getting coverart queue data

* Modified test

* Corrected endpoint syntax

* Fixed test

* Some cleanup

* Formatting
2025-06-17 21:40:49 -04:00
KD 6f2e8d6e17 Update song queue flow (#142)
* Added new status and made changes to song queue status code

* Saving changes

* Got a test working

* Got another test working

* Got the last test working

* Code cleanup

* More minor changes

* More minor changes

* Minor changes
2025-06-07 14:47:42 -04:00
KD eb1f19a96e Refactoring tests (#141)
* Saving changes

* Got test working

* Removing commented code

* Code formatting

* Refactoring test

* Code refactoring

* More refactoring

* Code refactoring

* Last bit of tidying up
2025-06-04 20:40:41 -04:00
KD bb766acf4e Added icarus_envy (#140)
* Added icarus_envy

* Commenting out dotenvy crate

* Removing environment and keys modules

* Replacing with icarus_envy

* Making changes to test code

* Removing lingering references in tests

* Test fix

* Test fix

* Another test fix

* Rust formatting

* Removing commented crate

* Using v0.2.0 version of icarus_envy
2025-06-01 20:20:33 -04:00
KD 95688cccfa Including Cargo.lock in source control (#139)
* Removing cargo.lock from gitignore

* Adding cargo.lock
2025-06-01 19:15:33 -04:00
KD e2485cec44 Add sample docker env (#138)
* Added sample .env.docker file

* Updated sample .env file
2025-05-28 19:41:38 -04:00
KD df1562f5e7 Updated auth environment variables in docker (#137) 2025-05-27 16:59:20 -04:00
KD e00e9cf185 Wipe data from coverart queue (#136)
* Changes to coverartQueue migrations

* Added endpoint to wipe data from coverart queue

* Added constant for endpoint

* Endpoint is now available

* Added test

Need to complete

* Test is working

* Code formatting

* Removed TODO
2025-05-26 17:12:11 -04:00
KD f0d2b9de71 Wipe data from song queue (#135)
* Added TODOs for later

* Added endpoint to wipe data from song queue

* Migration changes

* Syntax error fix

* Added and linked endpoint

* Added test

* Warning fixes

* Code formatting
2025-05-25 21:12:17 -04:00
KD f0a0bee22b Create coverart (#134)
* Added migrations for coverart

* Test refactor

* Code formatting

* Added endpoint to create coverart

* Endpoint is available

* Migration changes to song table

* some fixes

* Added test

* Test refactor

* Code formatting

* Clippy warning fixes

* Code cleanup
2025-05-24 19:02:48 -04:00
KD 0b73c055aa Create metadata (#133)
* Added migration for metadata table used to store a song's metadata

* Added comment for new table

* Added endpoint to create metadata

* Filling out code

* Changed type of duration in request

* Reorganizing code

* Changing metadata table to song

* Added TODO for data type discrepancy for the future

* Fix migration syntax error

* Added types to request

* Added environment variable for root directory:

* Moving keys to its own module

* Moved environement related code to its own module

* Added function to get root_directory

* Added endpoint to create song

* Minor fixes

* Added test and refactored tests

* Ironing out some kinks

* Resolved test issue

Turns out I was using the wrong id value

* Added env variable in workflow

* Added TODO

* Updated env variable

* clippy warning fixes

* Making sure date_created is populated when returneD

* Code cleanup

* Added TODOs

* Uncommenting tests

* Code formatting
2025-05-23 20:12:15 -04:00
KD d4c025511d Updating song from queue (#132)
* Added endpoint - no code - for updating the song from the queue

* Added endpoint to update song from queue

* Endpoint is now available

* Fixed endoint uri

* Added tempfile crate for tests

* Test is working

* Test is operational

* Code formatting

* Code cleanup
2025-05-22 16:18:12 -04:00
KD b2a4faec76 Coverart queue fetch data (#131)
* Added coverart queue data fetch endpoint

* Added test

* Code formatting
2025-05-22 15:03:57 -04:00
KD 7d27aed314 Coverart queue fetch without data (#130)
* Added endpoint

* Endpoint is now accessible

* Moved request to its own function in tests

* Added test, but running into issues

* Fixing code causing test to fail

* Code formatting
2025-05-21 19:26:38 -04:00
KD 961f99b744 Update song queue status (#129)
* Changes to status definition

* Added endpoint to update song_queue status

* endpoint return fix

* Endpoint now available

* Added test

* Code formatting

* Clippy warning fix
2025-05-20 20:42:34 -04:00
KD b1f5c5f604 Link coverart with song queue (#128)
* Added initial code to link coverart id and song queue id

* Added more functionality

* Preparing test

* Finished test

* Cleanup

* Code formatting

* Clippy warning fix
2025-05-19 21:54:50 -04:00
KD ddb0bf27f8 Queue coverart (#127)
* Added new module

* Added new endpoint

* Formatting

* Migration changes

* Fix routing issue

* Fixed migration

* Added functionality and test

* Test change

* Cleanup

* Code formatting
2025-05-18 18:22:39 -04:00
KD d491767de4 Get metadata (#126)
* Added time crate

* Added endpoint to get metadata

* Formatting

* Added more code to get metadata

Having issues returning response

* Formatting

* Migration change

* Fix date issue

* Got it somewhat working

* Got things working

* Code fix

* Added test

* Formatting

* Test works

* Cleanup

* Migrations cleanup
2025-04-29 21:52:22 -04:00
KD 8755276b48 Fetch next queued song (#125)
* Added code

* Got it working

* Added test and refactored test code

* Removed comment

* Cleanup

* Code formatting
2025-04-26 18:38:17 -04:00
KD fd41d25c4c Test refactor (#124)
* Refactoring test

* Test refactor

* Added code to get response content

* More refactoring

* Code refactor
2025-04-26 16:56:09 -04:00
KD 5e3ca1861d Next queued song (#123)
* Added code to fetch next queue item

* Updated README

* Give it a go

* Minor docker compose change

* Added docker-compose defaults

* Code refactor

* Removed data from being fetched

* Added test

* Formatting
2025-04-26 16:11:46 -04:00
KD db7350b550 Added status to songQueue table (#122)
* Added status to songQueue table

* Added status

* Formatting

* Removed failed option for the status field for now
2025-04-24 21:11:45 -04:00
KD f823a99984 Queue meta data (#121)
* Added migrations

Added new table to store metadata

* Updated table

* Added code for new endpoint

* Updated module

* Code formatting:

* Added test code

* Fixed test

* Cleanup

* Code refactor
2025-04-23 20:50:07 -04:00
KD 2823452198 Added callers moulde (#120)
* Added callers moulde

* Code formatting

* Added uuid dependency

* Added queue_song endpoint

* Added endpoint to router

* Added dependencies for postgres

* Added migrations directory

* Added more code

* Added migrations

* Removed migration

* Docker fixes

* Updated gitignore

* Updated env sample file

* Endpoint url fix

* Updated env sample

* Updated docker file to copy migrations

* Might undo later

* Updated env sample and docker compose

* Updated cargo dependencies

* Saving changes

* Workflow fix

* Will need to take another look at the dependencies again

* Added code

* Test is not finished yet

* Warning fix for now

* Updated workflow to setup database

* Workflow fix

* Workflow fix attempt

* Warning fix

* Warning fix

* Updated workflow

* Added test files

Might change later

* Added more crates

* Added more test code

Not finished

* Formatting

* Close, but far

* I believe it is now working

* Formatting

* Added more crates

* Code cleanup

* Code reformatting

* Code cleanup

* Crate cleanup

* Cleanup
2025-04-22 20:08:24 -04:00
KD 0ba2d4e8b9 Updated workflow (#119) 2025-04-20 14:05:03 -04:00
KD 4fdd660988 Updated packages (#118)
* Updated tokio

* Updated serde packages

* Updated icarus related dependencies
2025-04-20 14:01:33 -04:00
KD b6fe9a070c Added Docker file (#117)
* Added Docker file

* Added docker files

* Updated docker

Added icarus_auth and second db

* Cleanup

* Docker changes

* Saving changes

Still running into issues with icarus_auth starting in docker. Must be related to credentials or url

* Changes to docker

* Got auth_api working

* Cleanup

* Docker cleanup

* Updated gitignore

* Added README

* Updated gitignore

* Added directory to ignore to dockerignore

* File name change
2025-04-13 14:53:05 -04:00
KD 6e4eeafe13 Added .env.sample file (#116)
* Added .env.sample file

* Adedd variables

* Using default info
2025-04-12 15:56:10 -04:00
KD 6f320acb7c V0.2 web api (#113)
* Adding dependencies

* Added web api code

* Some changes

* Code formatting

* Added dependencies

*   *

* Added workflow

* Changed branch to trigger workflows

* Added ssh key import

* Warning fixes
2025-03-30 15:00:07 -04:00
KD 9dd7ffa2b6 Updated gitignore 2025-03-28 19:04:35 -04:00
KD fbcbd41809 Added initial rust code 2025-03-28 19:03:51 -04:00
KD 01d315e25e Updating gitignore 2025-03-28 19:03:03 -04:00
KD caf9f08d8a Forgot to remove this files 2025-03-28 19:02:31 -04:00
KD 7227eb9420 Removing dotnet files 2025-03-28 19:00:05 -04:00
KD 12c4f56a22 Merge pull request #112 from kdeng00/version_bump
Version bump
2025-03-28 18:50:37 -04:00
KD 6d650b7962 Delete song endpoint Bug fix (#111)
* Bug fix

* Removed some code

* Simplifying method
2025-03-28 18:49:34 -04:00
KD 73bb417bde Version bump 2025-03-28 18:48:02 -04:00
KD b0277077f4 Version bump (#110) 2025-03-11 22:09:57 -04:00
KD 3edf6424d0 Updated packages (#109) 2025-03-11 22:04:45 -04:00
KD dcb51448aa tsk-76: Added model and context for AccessLevel (#108)
* tsk-76: Added model and context for AccessLevel

* Formatted code

* AccessLevel migrations are working

* minor formatting

* tsk-#76: Saving AccessLevel when uploading song

* tsk-#76: Added endpoints

* tsk-#76: Warning fix

* tsk-#76: Adding more controller code

* Functionality to modify access levels is operational

* Formatting

* tsk-#76: Added functionality

* tsk-#76: Formatting code

* tsk-#76: Adding more code

* tsk-#76: Formatting
2025-03-11 21:45:32 -04:00
KD 9d90247bf8 Suppressing warning for endpoints (#107)
* Suppressing warning for endpoints

* Minor refactoring
2025-03-01 16:36:02 -05:00
KD 86e54e086a #91: Deprecating upload endpoint (#106)
* #91: Deprecating upload endpoint

* #91: Marking method as obsolete and left a todo

* #91: Removed methods
2025-02-27 19:35:39 -05:00
KD fd3ce9f96a #103: Remove WAV support (#105)
* #103: Adding checks to prevent .wav files from being uploaded:

* #103: Added method to create song and moving some code around

* #103: Fixed build issue

* tsk-103: Cleanup of temporary files

* tsk-103: Formatting changes

* tsk-103: Minor changes

* tsk-103: Fixing build issue

* tsk-103: Refactored enum

* CORE-23734: Refactoring

* tsk-103: Confirmed functionality is working

* Removed commented code

* Removed commented code
2025-02-16 17:24:50 -05:00
KD a89580ac70 #102: Moving Models and constants to their own library (#104)
* #102: Moving Models and constants to their own library

* #102: Updated sln file

* #102: Updated README to include step install migration tool
2024-08-05 20:05:57 -04:00
KD 8900afdfd2 #93: Add flac support (#101)
* Added script shell to convert wav files to flac

* #93: Minor change to script to convert wav files to flac

* #93: Added more code to for the music file conversion

* #93: Script to convert wav files to flac files is done

* #93: Clean up

* #93: Starting work to implement flac support

* #93: Minor update to conversion script

* #93: Added dotnet manifest file

Includes the dotnet-ef tool

* #93: Updated README and migration script

* #93: Created skeleton for saving flac files

* #93: Making some changes to how the file extension is determined

* #93: Updated gitignore

* #93: Another gitignore update

* #93: Making some changes to the process of saving a song to the filesystem

* #93: Adding SQL script to drop and create database

* #93: Saving uploaded song to a temporary location for processing

* #93: Initializing some properties in the song model earlier

* #93: Uploading wav songs works. Moving on to flac support

* #93: Added more code to save flac files

* #93: Updated some code that affects filename generation for audio files

* #93: Song Model changes and support for different file types

* #93: Working on build errors, removed DotnetZip dependency, and added new Ionic.Zlip.Netstandard dependency

* #93: Reduce build errors

* #93: Removed ID3 dependency

* #93: Cleanup
2024-07-27 13:19:12 -04:00
Kun Deng 4b3fa78336 #95: Id changes to models (#99)
* #95: Updated Id of the CoverArt model

* Model and migration changes
2024-06-21 20:30:23 -04:00
Kun Deng 71656aa940 #94: Saving the file type of cover art, but there is still some work to do (#100)
* #94: Saving the file type of cover art, but there is still some work to do

The filename of cover art is hard coded to have the png extension. Need to correct this

* #94: Added a TODO

* #94: Corrected file extension being saved in the db

* Cleanup

* Saving databasescripts
2024-06-21 20:26:44 -04:00
Kun Deng 562bc87822 #74: Add UserID to the Song model (#98)
* #74: Add UserID to the Song model

* #74: Added functionality to retrieve user id from the token

* #74: Songs will now contain the User Id when uploading

* Updated Readme and script to add migrations

* #74: Some cleanup
2024-06-19 15:41:55 -04:00
Kun Deng ebb15b7cdb #79: Added query parameter to randomize filename when downloading songs (#96)
* #79: Added query parameter to randomize filename when downloading songs

Right now only the  endpoint has this parameter added

* #79: Added functionality to the comressed song endpoint

* Added functionality to the download cover art
2024-06-16 19:04:10 -04:00
Kun Deng 270b7d059c #90: Adding functionality to download cover art (#97)
* Added endpoint to download cover art

* Code cleanup

* More cleanup
2024-06-16 18:28:08 -04:00
Kun Deng 23c50de468 Net8 (#89)
* .NET 8 migration

* Fixed bugs with song management

* More cleanup and making some constants

* Updated yml

* Clean up

Removing comments, some cleanup, and moving the startup code into Program.cs

* Removing comments

* Fixed song deletion issue

* Added functionality to delete song directories

* Updated README
2024-06-15 12:17:12 -04:00
kdeng00 ececcb5ba3 Cover art changes 2024-06-09 13:10:51 -04:00
Kun Deng 8658dd34ab Merge pull request #87 from kdeng00/update_namespace
Migrating to modern c# namespace
2023-04-09 12:43:48 -04:00
kdeng00 c17c9cd329 Migrating to modern c# namespace
Using the short namesapce declaration for conciseness
2023-04-09 12:34:06 -04:00
Kun Deng 37cfda84b5 Merge pull request #86 from kdeng00/deprecate_asymmetric_keys
Deprecating asymmetric keys. Closes #84
2022-12-16 14:09:42 -05:00
Kun Deng 3ee17a77a8 Merge pull request #83 from kdeng00/dependabot/nuget/Newtonsoft.Json-13.0.2
Bump Newtonsoft.Json from 13.0.1 to 13.0.2
2022-12-16 14:07:06 -05:00
kdeng00 522be59973 Removing deprecated call
Removing method call that has been deprecated from one of the Controller classes
2022-12-16 13:56:56 -05:00
kdeng00 712a0a9a4a Updated config file 2022-12-16 13:54:18 -05:00
kdeng00 74569d85b6 Deprecating asymmetric keys
Starting work to deprecate using public and private keys to sign tokens
2022-12-16 13:48:15 -05:00
Kun Deng b660f8361f Merge pull request #85 from kdeng00/link_token_with_user
Token modification.  Closes #75
2022-12-16 13:04:37 -05:00
kdeng00 d460b5d5d6 Token modification
Addresses #84. Adding user ID to the access token.
2022-12-16 12:59:11 -05:00
dependabot[bot] 4a1c1f1f78 Bump Newtonsoft.Json from 13.0.1 to 13.0.2
Bumps [Newtonsoft.Json](https://github.com/JamesNK/Newtonsoft.Json) from 13.0.1 to 13.0.2.
- [Release notes](https://github.com/JamesNK/Newtonsoft.Json/releases)
- [Commits](https://github.com/JamesNK/Newtonsoft.Json/compare/13.0.1...13.0.2)

---
updated-dependencies:
- dependency-name: Newtonsoft.Json
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-08 15:46:41 +00:00
Kun Deng 66b5944f92 Merge pull request #81 from kdeng00/pre-release
Pre release
2022-09-05 17:13:11 -04:00
Kun Deng 80628292e2 Merge pull request #80 from kdeng00/v0.1.20
V0.1.20
2022-09-05 17:12:57 -04:00
Kun Deng 57f4ec1261 Merge pull request #78 from kdeng00/add_swagger_docs
Swagger docs. Close #71
2022-09-05 17:09:23 -04:00
Kun Deng c191d7ee7a Update README.md
Note about swagger
2022-09-05 17:07:26 -04:00
Kun Deng 73bfff3940 Cleanup 2022-09-05 17:02:25 -04:00
Kun Deng 38c056cc99 Swagger docs 2022-09-05 16:58:10 -04:00
Kun Deng 1643f78720 Delete v0.2.yml 2022-09-04 21:29:10 -04:00
Kun Deng 06bb52dfa5 v0.2 Branch 2022-09-04 21:23:11 -04:00
Kun Deng ab89d1602f Create dotnet.yml 2022-09-04 21:20:53 -04:00
Kun Deng 640971dea8 Merge pull request #77 from kdeng00/jwt_symmetric_keys
JWT symmetric keys.

This closes #73.
2022-09-04 20:58:13 -04:00
Kun Deng 664b0d520b Updated Readme 2022-09-04 20:56:37 -04:00
Kun Deng 11fe1f29e1 Updated Readme 2022-09-04 20:50:35 -04:00
Kun Deng 05b5de0939 Cleanup 2022-09-04 20:45:17 -04:00
Kun Deng d5944c470b Symmetric keys with jwt has been implemented 2022-09-04 20:34:33 -04:00
Kun Deng 00ef8d0242 Added JWT bearer authentication 2022-09-04 18:56:56 -04:00
Kun Deng 77c6ee00ea JWT symmetric keys
Updated config file to store neccessary information
2022-09-04 18:39:48 -04:00
Kun Deng 1bfd0a4931 Update Icarus.csproj 2022-08-30 21:42:20 -04:00
Kun Deng 6c3ce69873 Merge pull request #72 from kdeng00/remove_auth
Remove auth
2022-08-30 21:40:26 -04:00
Kun Deng a423e6e220 Update README.md
Updated to reflect removing dependence on Auth0.
2022-08-30 21:39:47 -04:00
Kun Deng f8ec65fd29 Tokens are validated in respective endpoints 2022-08-27 21:35:49 -04:00
Kun Deng 665407aac5 Code cleanup 2022-08-27 20:35:34 -04:00
Kun Deng e5bea187f4 Token validation 2022-08-27 20:16:01 -04:00
Kun Deng fd0f487615 Runtime fixes
Fixed issues preventing endpoints from successfully completing
2022-08-26 23:09:48 -04:00
Kun Deng d48716a54f Endpoint authorization
Tokens are validated for some of the endpoints. Need to add other measures to rule out bogus tokens.
2022-08-26 18:01:29 -04:00
Kun Deng 2cc13ac9bb Code changes
Did not run code. Had a spur of the moment to try something out.
2022-08-25 20:56:12 -04:00
Kun Deng 467a9d7e0f Saving changes
Using asymmetric validation
2022-08-13 18:23:03 -04:00
kdeng00 f0551a4801 Updates
Added packages and updated readme
2022-08-07 17:05:49 -04:00
kdeng00 2cd8c83e28 Updated config 2022-08-07 17:03:47 -04:00
Kun Deng 9797897b76 Added solution file 2022-06-30 21:44:28 -04:00
kdeng00 27cc80d114 Ready for release 2022-01-09 18:13:31 -05:00
kdeng00 27ee3d6ba5 Updated User model 2022-01-09 17:21:26 -05:00
kdeng00 1b6e70ce12 More cleanup 2021-12-29 14:27:04 -05:00
kdeng00 5669cdbdd0 Code cleanup 2021-12-29 14:13:38 -05:00
kdeng00 3a6e0f64bc New endpoint
New endpoint to upload song with metadata and cover art as separate entities
2021-12-29 14:06:52 -05:00
kdeng00 aa0a1ab5fe Refactoring 2021-12-26 22:17:14 -05:00
kdeng00 b529731c95 Song uploads
Uploading songs is functional and does not use the song metadata for the filename or directory creation
2021-12-25 21:36:42 -05:00
kdeng00 64e6f33d7c Refactoring 2021-12-25 11:28:02 -05:00
kdeng00 023298c6f8 Refactoring 2021-12-24 20:51:15 -05:00
kdeng00 f8d9c8e4a7 Authorization functioning 2021-12-24 16:45:40 -05:00
kdeng00 471fa71789 Updated Readme 2021-12-23 21:12:50 -05:00
kdeng00 edaea68296 Functional 2021-12-23 21:10:13 -05:00
kdeng00 8600d9b6bc Updates 2021-12-22 21:33:12 -05:00
kdeng00 922e527819 Removing repositories 2021-12-22 20:28:20 -05:00
kdeng00 46f0da4a5f Updated to .NET 6 2021-12-22 20:20:22 -05:00
kdeng00 16839b9bf8 Saving changes 2021-12-22 20:18:27 -05:00
Kun Deng 552d5dcd25 Merge pull request #70 from kdeng00/dependency-update
Dependency update
2021-08-01 20:08:26 -04:00
kdeng00 cadcf33ed6 Updated dependency 2021-08-01 20:07:40 -04:00
kdeng00 4f46f2ed94 Updated dependencies 2021-08-01 19:50:26 -04:00
kdeng00 560773bfff Now buildable targetting .net 3.1 2021-08-01 19:45:21 -04:00
kdeng00 43623d07c3 Removing unneeded file 2021-08-01 19:17:54 -04:00
kdeng00 b93d436c29 Removing user file 2021-08-01 19:15:26 -04:00
132 changed files with 7588 additions and 7873 deletions
+21
View File
@@ -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/
+7
View File
@@ -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}
+7
View File
@@ -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}
+50
View File
@@ -0,0 +1,50 @@
name: Release Tagging
on:
push:
branches: [ "v0.2" ]
jobs:
release:
runs-on: ubuntu-24.04
steps:
- name: Checkout code
uses: actions/checkout@v3
with:
fetch-depth: 0 # Important for git describe --tags
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: 1.88.0
components: cargo
- name: Extract Version from Cargo.toml
id: version
run: |
VERSION=$(grep '^version = "' Cargo.toml | sed -E 's/version = "([^"]+)"/\1/')
PROJECT_COMMIT_HASH=$(git rev-parse HEAD | cut -c 1-10)
BRANCH_REF="${GITHUB_REF}"
BRANCH_NAME=$(echo "$BRANCH_REF" | cut -d '/' -f 3)
PROJECT_TAG_RELEASE="v$VERSION-$BRANCH_NAME-$PROJECT_COMMIT_HASH"
echo "::set-output name=project_tag_release::$PROJECT_TAG_RELEASE-961"
echo "Version: $VERSION"
echo "Hash: $PROJECT_COMMIT_HASH"
echo "Branch: $BRANCH_NAME"
echo "Tag Release: $PROJECT_TAG_RELEASE"
- name: Print version
id: print_version
run: |
echo "Printing version"
echo "Version: ${{ steps.version.outputs.project_tag_release }}"
- name: Create GitHub Release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ steps.version.outputs.project_tag_release }}
release_name: Release ${{ steps.version.outputs.project_tag_release }}
body: |
Release of version ${{ steps.version.outputs.project_tag_release }}
+77
View File
@@ -0,0 +1,77 @@
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: cargo test --verbose
- name: Run clippy
run: cargo clippy -- -D warnings
- name: Run rustfmt
run: cargo fmt --all -- --check
+8 -7
View File
@@ -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/
-21
View File
@@ -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
+8
View File
@@ -0,0 +1,8 @@
{
"dev": {
"migrations": "./migrations"
},
"test": {
"migrations": "./test_migrations"
}
}
-1
Submodule 3rdparty/cpr deleted from feebd2fd54
-1
Submodule 3rdparty/jwt-cpp deleted from 27f32983fb
Submodule 3rdparty/libbcrypt deleted from 8aa32ad94e
-1
Submodule 3rdparty/oatpp deleted from 5b4b313a0b
-1
Submodule 3rdparty/taglib deleted from 79bc9ccf8e
-149
View File
@@ -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})
-67
View File
@@ -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/
Generated
+2778
View File
File diff suppressed because it is too large Load Diff
+28
View File
@@ -0,0 +1,28 @@
[package]
name = "icarus"
version = "0.1.98"
edition = "2024"
rust-version = "1.88"
[dependencies]
axum = { version = "0.8.4", features = ["multipart"] }
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 = ["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"] }
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.1-devel-1c5de9dc26-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" }
+70
View File
@@ -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"]
Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

-21
View File
@@ -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.
+9 -169
View File
@@ -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)
![image](https://user-images.githubusercontent.com/14333136/56252069-28532d00-6084-11e9-896d-1a3c378014ef.png)
## 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
-26
View File
@@ -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
-86
View File
@@ -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)
);
-6
View File
@@ -1,6 +0,0 @@
delete from Song;
delete from Album;
delete from Artist;
delete from Genre;
delete from Year;
delete from CoverArt;
-1
View File
@@ -1 +0,0 @@
select * from Song;
-22
View File
@@ -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/"
}
-6
View File
@@ -1,6 +0,0 @@
{
"domain": "[domain].auth0.com",
"api_identifier": "https://[identifier]/api",
"client_id": "dfdfdfdf",
"client_secret": "dfdfdfdf"
}
-5
View File
@@ -1,5 +0,0 @@
[requires]
jsonformoderncpp/3.7.0@vthiery/stable
[generators]
cmake
-7
View File
@@ -1,7 +0,0 @@
{
"server": "localhost",
"database": "Icarus",
"username": "icarus-admin",
"password": "dreamofpeace"
}
+112
View File
@@ -0,0 +1,112 @@
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:
- main-api-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:
- auth-api-network
# Optional: Mount local code for development (live reload)
# volumes:
# - ./path/to/your/web-api-repo:/app
# 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:
- main-api-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:
- auth-api-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:
main-api-network:
driver: bridge
auth-api-network:
driver: bridge
-26
View File
@@ -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
-37
View File
@@ -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
-102
View File
@@ -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
-100
View File
@@ -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
-121
View File
@@ -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
-100
View File
@@ -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
-61
View File
@@ -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
-41
View File
@@ -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
-240
View File
@@ -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
-99
View File
@@ -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
-40
View File
@@ -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
-40
View File
@@ -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
-36
View File
@@ -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
-39
View File
@@ -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
-40
View File
@@ -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
-39
View File
@@ -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
-58
View File
@@ -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
-40
View File
@@ -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
-23
View File
@@ -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
-22
View File
@@ -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
-22
View File
@@ -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
-22
View File
@@ -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
-47
View File
@@ -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
-34
View File
@@ -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
-22
View File
@@ -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
-24
View File
@@ -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
-25
View File
@@ -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
-25
View File
@@ -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
-32
View File
@@ -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
-37
View File
@@ -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
-25
View File
@@ -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
-51
View File
@@ -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
-40
View File
@@ -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
-25
View File
@@ -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
-25
View File
@@ -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
-176
View File
@@ -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
-14
View File
@@ -1,14 +0,0 @@
#ifndef ALBUMFILTER_H_
#define ALBUMFILTER_H_
namespace type
{
enum class AlbumFilter
{
id = 0,
title,
year
};
}
#endif
-13
View File
@@ -1,13 +0,0 @@
#ifndef ARTISTFILTER_H_
#define ARTISTFILTER_H_
namespace type
{
enum class ArtistFilter
{
id = 0,
artist
};
}
#endif
-14
View File
@@ -1,14 +0,0 @@
#ifndef COVERFILTER_H_
#define COVERFILTER_H_
namespace type
{
enum class CoverFilter
{
id = 0,
songTitle,
imagePath
};
}
#endif
-13
View File
@@ -1,13 +0,0 @@
#ifndef GENREFILTER_H_
#define GENREFILTER_H_
namespace type
{
enum class GenreFilter
{
id = 0,
category
};
}
#endif
-15
View File
@@ -1,15 +0,0 @@
#ifndef PATHTYPE_H_
#define PATHTYPE_H_
namespace type
{
enum PathType
{
music = 0,
archive,
temp,
coverArt
};
}
#endif
-13
View File
@@ -1,13 +0,0 @@
#ifndef SALTFILTER_H_
#define SALTFILTER_H_
namespace type {
enum class SaltFilter
{
id = 0,
salt,
userId
};
}
#endif
-23
View File
@@ -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
-16
View File
@@ -1,16 +0,0 @@
#ifndef SONGCHANGED_H_
#define SONGCHANGED_H_
namespace type
{
enum SongChanged
{
title = 0,
artist,
album,
genre,
year
};
}
#endif
-17
View File
@@ -1,17 +0,0 @@
#ifndef SONGFILTER_H_
#define SONGFILTER_H_
namespace type
{
enum class SongFilter
{
id = 0,
title,
album,
artist,
genre,
year
};
}
#endif
-12
View File
@@ -1,12 +0,0 @@
#ifndef USERFILTER_H_
#define USERFILTER_H_
namespace type {
enum class UserFilter
{
id = 0,
username
};
}
#endif
-13
View File
@@ -1,13 +0,0 @@
#ifndef YEARFILTER_H_
#define YEARFILTER_H_
namespace type
{
enum class YearFilter
{
id = 0,
year
};
}
#endif
-32
View File
@@ -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
-26
View File
@@ -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
-19
View File
@@ -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
-25
View File
@@ -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
);
-6
View File
@@ -1,6 +0,0 @@
{
"root_music_path": "/icarus/music/",
"temp_root_path": "/icarus/temp/",
"cover_root_path": "/icarus/cover/",
"archive_root_path": "icarus/archive/"
}
-82
View File
@@ -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;
}
-44
View File
@@ -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;
}
}
+685
View File
@@ -0,0 +1,685 @@
// 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 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(),
),
}
}
}
+281
View File
@@ -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))
}
},
}
}
}
+29
View File
@@ -0,0 +1,29 @@
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 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";
}
+1097
View File
File diff suppressed because it is too large Load Diff
-449
View File
@@ -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;
}
-415
View File
@@ -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;
}
-96
View File
@@ -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;
}
-374
View File
@@ -1,374 +0,0 @@
#include "database/CoverArtRepository.h"
#include <iostream>
#include <memory>
#include <sstream>
#include <string>
#include <cstring>
database::CoverArtRepository::CoverArtRepository(const std::string& path) : BaseRepository(path)
{ }
database::CoverArtRepository::CoverArtRepository(const model::BinaryPath& bConf) : BaseRepository(bConf)
{ }
std::vector<model::Cover> database::CoverArtRepository::retrieveRecords()
{
auto conn = setupMysqlConnection();
auto stmt = mysql_stmt_init(conn);
const std::string query = "SELECT * FROM CoverArt";
mysql_stmt_prepare(stmt, query.c_str(), query.size());
mysql_stmt_execute(stmt);
auto coverArts = parseRecords(stmt);
mysql_stmt_close(stmt);
mysql_close(conn);
return coverArts;
}
model::Cover database::CoverArtRepository::retrieveRecord(model::Cover& cov, type::CoverFilter filter = type::CoverFilter::id)
{
std::stringstream qry;
auto conn = setupMysqlConnection();
auto stmt = mysql_stmt_init(conn);
qry << "SELECT * FROM CoverArt WHERE ";
MYSQL_BIND params[1];
memset(params, 0, sizeof(params));
auto songTitleLength = cov.songTitle.size();
switch (filter) {
case type::CoverFilter::id:
qry << "CoverArtId = ?";
params[0].buffer_type = MYSQL_TYPE_LONG;
params[0].buffer = (char*)&cov.id;
params[0].length = 0;
params[0].is_null = 0;
break;
case type::CoverFilter::songTitle:
qry << "SongTitle = ?";
params[0].buffer_type = MYSQL_TYPE_STRING;
params[0].buffer = (char*)cov.songTitle.c_str();
params[0].length = &songTitleLength;
params[0].is_null = 0;
break;
default:
break;
}
qry << " LIMIT 1";
const std::string 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 covDb = parseRecord(stmt);
mysql_stmt_close(stmt);
mysql_close(conn);
std::cout << "retrieved cover art record" << std::endl;
return covDb;
}
bool database::CoverArtRepository::doesCoverArtExist(const model::Cover& cover, type::CoverFilter 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 CoverArt WHERE ";
auto titleLength = cover.songTitle.size();
switch (filter) {
case type::CoverFilter::id:
qry << "CoverArtId = ?";
params[0].buffer_type = MYSQL_TYPE_LONG;
params[0].buffer = (char*)&cover.id;
params[0].length = 0;
params[0].is_null = 0;
break;
case type::CoverFilter::songTitle:
qry << "SongTitle = ?";
params[0].buffer_type = MYSQL_TYPE_STRING;
params[0].buffer = (char*)cover.songTitle.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;
}
// TODO: change to prepared statement
void database::CoverArtRepository::deleteRecord(const model::Cover& cov)
{
auto conn = setupMysqlConnection();
const std::string query("DELETE FROM CoverArt WHERE CoverArtId = " + std::to_string(cov.id));
auto result = performMysqlQuery(conn, query);
mysql_close(conn);
}
void database::CoverArtRepository::saveRecord(const model::Cover& cov)
{
std::cout << "saving cover art record";
auto conn = setupMysqlConnection();
auto stmt = mysql_stmt_init(conn);
MYSQL_BIND params[2];
memset(params, 0, sizeof(params));
my_bool isNull;
const std::string query = "INSERT INTO CoverArt(SongTitle, ImagePath) VALUES(?, ?)";
auto status = mysql_stmt_prepare(stmt, query.c_str(), query.size());
params[0].buffer_type = MYSQL_TYPE_STRING;
params[0].buffer = (char*)cov.songTitle.c_str();
auto songTitleLength = cov.songTitle.size();
params[0].length = &songTitleLength;
params[0].is_null = 0;
params[1].buffer_type = MYSQL_TYPE_STRING;
params[1].buffer = (char*)cov.imagePath.c_str();
auto imagePathLength = cov.imagePath.size();
params[1].length = &imagePathLength;
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 << "saved cover art record" << std::endl;
}
void database::CoverArtRepository::updateRecord(const model::Cover& cover)
{
std::stringstream qry;
auto conn = setupMysqlConnection();
auto stmt = mysql_stmt_init(conn);
qry << "UPDATE CoverArt SET ";
qry << "SongTitle = ?, ";
qry << "ImagePath = ? ";
qry << "WHERE CoverArtId = ?";
MYSQL_BIND params[3];
memset(params, 0, sizeof(params));
auto songTitleLength = cover.songTitle.size();
auto imagePathLength = cover.imagePath.size();
params[0].buffer_type = MYSQL_TYPE_STRING;
params[0].buffer = (char*)cover.songTitle.c_str();
params[0].length = &songTitleLength;
params[0].is_null = 0;
params[1].buffer_type = MYSQL_TYPE_STRING;
params[1].buffer = (char*)cover.imagePath.c_str();
params[1].length = &imagePathLength;
params[1].is_null = 0;
params[2].buffer_type = MYSQL_TYPE_LONG;
params[2].buffer = (char*)&cover.id;
params[2].length = 0;
params[2].is_null = 0;
const std::string 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 << "updated cover art record" << std::endl;
}
std::vector<model::Cover> database::CoverArtRepository::parseRecords(MYSQL_STMT *stmt)
{
mysql_stmt_store_result(stmt);
auto rowCount = mysql_stmt_num_rows(stmt);
std::cout << "number of results " << rowCount << std::endl;
std::vector<model::Cover> coverArts;
coverArts.reserve(rowCount);
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::Cover coverArt;
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 songTitle[strLen];
char imagePath[strLen];
val[0].buffer_type = MYSQL_TYPE_LONG;
val[0].buffer = (char*)&coverArt.id;
val[0].length = &len[0];
val[0].is_null = &nullRes[0];
val[1].buffer_type = MYSQL_TYPE_STRING;
val[1].buffer = (char*)songTitle;
val[1].buffer_length = strLen;
val[1].length = &len[1];
val[1].is_null = &nullRes[1];
val[2].buffer_type = MYSQL_TYPE_STRING;
val[2].buffer = (char*)imagePath;
val[2].buffer_length = strLen;
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;
}
coverArt.songTitle = songTitle;
coverArt.imagePath = imagePath;
coverArts.push_back(std::move(coverArt));
}
}
std::cout << "fetching next result" << std::endl;
}
return coverArts;
}
model::Cover database::CoverArtRepository::parseRecord(MYSQL_RES *results)
{
std::cout << "parsing record" << std::endl;
model::Cover cov;
auto fieldNum = mysql_num_fields(results);
MYSQL_ROW row = mysql_fetch_row(results);
for (auto i = 0; i != fieldNum; ++i) {
switch (i) {
case 0:
cov.id = std::stoi(row[i]);
break;
case 1:
cov.songTitle = row[i];
break;
case 2:
cov.imagePath = row[i];
break;
}
}
std::cout << "done parsing record" << std::endl;
return cov;
}
model::Cover database::CoverArtRepository::parseRecord(MYSQL_STMT *stmt)
{
std::cout << "parsing cover art record" << std::endl;
mysql_stmt_store_result(stmt);
model::Cover cover;
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 songTitle[strLen];
char imagePath[strLen];
val[0].buffer_type = MYSQL_TYPE_LONG;
val[0].buffer = (char*)&cover.id;
val[0].length = &len[0];
val[0].is_null = &nullRes[0];
val[1].buffer_type = MYSQL_TYPE_STRING;
val[1].buffer = (char*)&songTitle;
val[1].buffer_length = strLen;
val[1].length = &len[1];
val[1].is_null = &nullRes[1];
val[2].buffer_type = MYSQL_TYPE_STRING;
val[2].buffer = (char*)&imagePath;
val[2].buffer_length = strLen;
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);
cover.songTitle = songTitle;
cover.imagePath = imagePath;
}
status = mysql_stmt_next_result(stmt);
}
std::cout << "done parsing cover art record" << std::endl;
return cover;
}

Some files were not shown because too many files have changed in this diff Show More