Compare commits

..

22 Commits

Author SHA1 Message Date
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
142 changed files with 4005 additions and 7557 deletions
-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
-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
+31
View File
@@ -0,0 +1,31 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Icarus.Authorization;
namespace Icarus.Authorization.Handlers
{
public class HasScopeHandler : AuthorizationHandler<HasScopeRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, HasScopeRequirement requirement)
{
if (!context.User.HasClaim(c => c.Type == "scope" && c.Issuer == requirement.Issuer))
{
return Task.CompletedTask;
}
var scopes = context.User.FindFirst(c =>
c.Type == "scope" && c.Issuer == requirement.Issuer).Value.Split(' ');
if (scopes.Any(s => s == requirement.Scope))
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
}
+19
View File
@@ -0,0 +1,19 @@
using System;
using Microsoft.AspNetCore.Authorization;
namespace Icarus.Authorization
{
public class HasScopeRequirement : IAuthorizationRequirement
{
public string Issuer { get; }
public string Scope { get; }
public HasScopeRequirement(string scope, string issuer)
{
Scope = scope ?? throw new ArgumentNullException(nameof(scope));
Issuer = issuer ?? throw new ArgumentNullException(nameof(issuer));
}
}
}
-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})
+11
View File
@@ -0,0 +1,11 @@
using System;
using System.IO;
namespace Icarus.Constants
{
public class DirectoryPaths
{
public static string CoverArtPath =>
Directory.GetCurrentDirectory() + "/Images/Stock/CoverArt.png";
}
}
+155
View File
@@ -0,0 +1,155 @@
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using Microsoft.Extensions.Configuration;
using Icarus.Controllers.Utilities;
using Icarus.Models;
using Icarus.Database.Contexts;
namespace Icarus.Controllers.Managers
{
public class AlbumManager : BaseManager
{
#region Fields
private AlbumContext _albumContext;
#endregion
#region Properties
#endregion
#region Constructors
public AlbumManager(IConfiguration config)
{
_config = config;
_connectionString = _config.GetConnectionString("DefaultConnection");
_albumContext = new AlbumContext(_connectionString);
}
#endregion
#region Methods
public void SaveAlbumToDatabase(ref Song song)
{
_logger.Info("Starting process to save the album record of the song to the database");
var album = new Album();
album.Title = song.AlbumTitle;
album.AlbumArtist = song.Artist;
album.Year = song.Year.Value;
var albumTitle = song.AlbumTitle;
var albumArtist = song.Artist;
var albumRetrieved = _albumContext.Albums.FirstOrDefault(alb => alb.Title.Equals(albumTitle) && alb.AlbumArtist.Equals(albumArtist));
if (albumRetrieved == null)
{
album.SongCount = 1;
_albumContext.Add(album);
_albumContext.SaveChanges();
Console.WriteLine($"Album Id {album.AlbumID}");
}
else
{
album.AlbumID = albumRetrieved.AlbumID;
}
song.AlbumID = album.AlbumID;
}
public void DeleteAlbumFromDatabase(Song song)
{
var album = _albumContext.Albums.FirstOrDefault(alb => alb.Title.Equals(song.AlbumTitle));
if (album == null)
{
_logger.Info("Cannot delete the album record because it does not exist");
return;
}
DeleteAlbumFromDb(album);
}
public Album UpdateAlbumInDatabase(Song oldSong, Song newSong)
{
var albumRecord = _albumContext.Albums.FirstOrDefault(alb => alb.Title.Equals(oldSong.AlbumTitle));
var oldAlbumTitle = oldSong.AlbumTitle;
var oldAlbumArtist = oldSong.Artist;
var newAlbumTitle = newSong.AlbumTitle;
var newAlbumArtist = newSong.Artist;
var info = string.Empty;
if (string.IsNullOrEmpty(newAlbumArtist))
newAlbumArtist = oldAlbumArtist;
if (string.IsNullOrEmpty(newAlbumTitle))
newAlbumTitle = oldAlbumTitle;
if ((string.IsNullOrEmpty(newAlbumTitle) && string.IsNullOrEmpty(newAlbumArtist) ||
oldAlbumTitle.Equals(newAlbumTitle) && oldAlbumArtist.Equals(newAlbumArtist)))
{
_logger.Info("No change to the song's album");
return albumRecord;
}
info = "Change to the song's album";
_logger.Info(info);
var existingAlbumRecord = _albumContext.Albums.FirstOrDefault(alb => alb.Title.Equals(oldSong.AlbumTitle));
if (existingAlbumRecord == null)
{
_logger.Info("Creating new album record");
var newAlbumRecord = new Album
{
Title = newAlbumTitle,
AlbumArtist = newAlbumArtist,
Year = newSong.Year.Value
};
_albumContext.Add(newAlbumRecord);
_albumContext.SaveChanges();
return newAlbumRecord;
}
else
{
_logger.Info("Updating existing album record");
existingAlbumRecord = _albumContext.Albums.FirstOrDefault(alb => alb.Title.Equals(newSong.AlbumTitle));
existingAlbumRecord.AlbumArtist = newAlbumArtist;
_albumContext.Update(existingAlbumRecord);
_albumContext.SaveChanges();
return existingAlbumRecord;
}
}
private void DeleteAlbumFromDb(Album album)
{
if (SongsInAlbum(album) <= 1)
{
_albumContext.Remove(album);
_albumContext.SaveChanges();
}
}
private int SongsInAlbum(Album album)
{
var sngContext = new SongContext(_connectionString);
var songs = sngContext.Songs.Where(sng => sng.AlbumID == album.AlbumID).ToList();
return songs.Count;
}
#endregion
}
}
+137
View File
@@ -0,0 +1,137 @@
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using Microsoft.Extensions.Configuration;
using Icarus.Controllers.Utilities;
using Icarus.Models;
using Icarus.Database.Contexts;
namespace Icarus.Controllers.Managers
{
public class ArtistManager : BaseManager
{
#region Fields
private ArtistContext _artistContext;
#endregion
#region Properties
#endregion
#region Constructors
public ArtistManager(IConfiguration config)
{
_config = config;
_connectionString = _config.GetConnectionString("DefaultConnection");
_artistContext = new ArtistContext(_connectionString);
}
#endregion
#region Methods
public void SaveArtistToDatabase(ref Song song)
{
_logger.Info("Starting process to save the artist record of the song to the database");
var artist = new Artist();
artist.Name = song.Artist;
artist.SongCount = 1;
var artistTitle = artist.Name;
var artistRetrieved = _artistContext.Artists.FirstOrDefault(art => art.Name.Equals(artistTitle));
if (artistRetrieved == null)
{
artist.SongCount = 1;
_artistContext.Add(artist);
_artistContext.SaveChanges();
}
else
{
artist.ArtistID = artistRetrieved.ArtistID;
}
song.ArtistID = artist.ArtistID;
}
public Artist UpdateArtistInDatabase(Song oldSongRecord, Song newSongRecord)
{
var oldArtistRecord = _artistContext.Artists.FirstOrDefault(art => art.Name.Equals(oldSongRecord.AlbumTitle));
var oldArtistName = oldArtistRecord.Name;
var newArtistName = newSongRecord.Artist;
if (string.IsNullOrEmpty(newArtistName) || oldArtistName.Equals(newArtistName))
{
_logger.Info("No change to the song's Artist");
return oldArtistRecord;
}
_logger.Info("Change to the song's record found");
if (oldArtistRecord.SongCount <= 1)
{
_logger.Info("Deleting artist record that no longer has any songs");
_artistContext.Remove(oldArtistRecord);
_artistContext.SaveChanges();
}
if (!(_artistContext.Artists.FirstOrDefault(art => art.Name.Equals(oldSongRecord.AlbumTitle)) != null))
{
_logger.Info("Creating new artist record");
var newArtistRecord = new Artist
{
Name = newArtistName
};
_artistContext.Add(newArtistRecord);
_artistContext.SaveChanges();
return newArtistRecord;
}
else
{
_logger.Info("Updating existing artist record");
var existingArtistRecord = _artistContext.Artists.FirstOrDefault(art => art.Name.Equals(newSongRecord.AlbumTitle));
_artistContext.Update(existingArtistRecord);
_artistContext.SaveChanges();
return existingArtistRecord;
}
}
public void DeleteArtistFromDatabase(Song song)
{
if (!(_artistContext.Artists.FirstOrDefault(art => art.Name.Equals(song.Artist)) != null))
{
_logger.Info("Cannot delete the artist record because it does not exist");
return;
}
var artist = _artistContext.Artists.FirstOrDefault(art => art.Name.Equals(song.Artist));
if (SongsOfArtist(artist) <= 1)
{
_artistContext.Remove(artist);
_artistContext.SaveChanges();
}
}
private int SongsOfArtist(Artist artist)
{
var sngContext = new SongContext(_connectionString);
var songs = sngContext.Songs.Where(sng => sng.ArtistID == artist.ArtistID).ToList();
return songs.Count;
}
#endregion
}
}
+16
View File
@@ -0,0 +1,16 @@
using System;
using Microsoft.Extensions.Configuration;
using NLog;
namespace Icarus.Controllers.Managers
{
public class BaseManager
{
#region Fields
protected static Logger _logger = NLog.Web.NLogBuilder.ConfigureNLog("nlog.config").GetCurrentClassLogger();
protected IConfiguration _config;
protected string _connectionString;
#endregion
}
}
+172
View File
@@ -0,0 +1,172 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Icarus.Constants;
using Icarus.Controllers.Utilities;
using Icarus.Database.Contexts;
using Icarus.Models;
using Icarus.Types;
namespace Icarus.Controllers.Managers
{
public class CoverArtManager : BaseManager
{
#region Fields
private string _rootCoverArtPath;
private CoverArtContext _coverArtContext;
private byte[] _stockCoverArt = null;
#endregion
#region Constructors
public CoverArtManager(IConfiguration config)
{
_config = config;
_connectionString = _config.GetConnectionString("DefaultConnection");
_rootCoverArtPath = _config.GetValue<string>("CoverArtPath");
Initialize();
}
#endregion
#region Methods
public void SaveCoverArtToDatabase(ref Song song, ref CoverArt coverArt)
{
_logger.Info("Saving cover art record to the database");
_coverArtContext.Add(coverArt);
_coverArtContext.SaveChanges();
song.CoverArtID = coverArt.CoverArtID;
}
public void DeleteCoverArtFromDatabase(CoverArt coverArt)
{
_logger.Info("Attempting to delete cover art from the database");
_coverArtContext.Remove(coverArt);
_coverArtContext.SaveChanges();
}
public void DeleteCoverArt(CoverArt coverArt)
{
try
{
var stockCoverArtPath = _rootCoverArtPath + "CoverArt.png";
if (!string.Equals(stockCoverArtPath, coverArt.ImagePath,
StringComparison.CurrentCultureIgnoreCase))
{
_logger.Info("Song does not contain the stock cover art");
File.Delete(coverArt.ImagePath);
_logger.Info("Cover art deleted from the filesystem");
}
else
{
_logger.Info("Song contains the stock cover art");
_logger.Info("Will not delete from from the filesystem");
}
}
catch (Exception ex)
{
var msg = ex.Message;
_logger.Error(msg, "An error occurred");
}
}
public CoverArt SaveCoverArt(Song song)
{
try
{
var dirMgr = new DirectoryManager(_rootCoverArtPath);
var defaultExtension = ".png";
dirMgr.CreateDirectory(song);
var coverArt = new CoverArt
{
SongTitle = song.Title
};
var segment = coverArt.GenerateFilename(0);
var imagePath = dirMgr.SongDirectory + segment + defaultExtension;
coverArt.ImagePath = imagePath;
var metaData = new MetadataRetriever();
var imgBytes = metaData.RetrieveCoverArtBytes(song);
if (imgBytes != null)
{
_logger.Info("Saving cover art to the filesystem");
File.WriteAllBytes(coverArt.ImagePath, imgBytes);
}
else
{
_logger.Info("Song has no cover art, applying stock cover art");
coverArt.ImagePath = _rootCoverArtPath + $"{segment}{defaultExtension}";
metaData.UpdateCoverArt(song, coverArt);
File.WriteAllBytes(coverArt.ImagePath, _stockCoverArt);
}
return coverArt;
}
catch (Exception ex)
{
var msg = ex.Message;
_logger.Error(msg, "An error occurred");
}
return null;
}
public CoverArt SaveCoverArt(IFormFile data, Song song)
{
var cover = new CoverArt { SongTitle = song.Title };
try
{
var dirMgr = new DirectoryManager(_rootCoverArtPath);
var defaultExtension = ".png";
dirMgr.CreateDirectory(song);
var segment = cover.GenerateFilename(0);
var imagePath = dirMgr.SongDirectory + segment + defaultExtension;
cover.ImagePath = imagePath;
using (var fileStream = new FileStream(imagePath, FileMode.Create))
{
data.CopyTo(fileStream);
}
}
catch (Exception ex)
{
_logger.Error(ex.Message, "An error occurred");
}
return cover;
}
public CoverArt GetCoverArt(Song song)
{
return _coverArtContext.CoverArtImages.FirstOrDefault(cov => cov.SongTitle.Equals(song.Title));
}
private void Initialize()
{
_coverArtContext = new CoverArtContext(_connectionString);
if (System.IO.File.Exists(DirectoryPaths.CoverArtPath))
_stockCoverArt = File.ReadAllBytes(DirectoryPaths.CoverArtPath);
if (!File.Exists(_rootCoverArtPath + "CoverArt.png"))
{
File.WriteAllBytes(_rootCoverArtPath + "CoverArt.png",
_stockCoverArt);
Console.WriteLine("Copied Stock Cover Art");
}
}
#endregion
}
}
+232
View File
@@ -0,0 +1,232 @@
using System;
using System.IO;
using System.Linq;
using Microsoft.Extensions.Configuration;
using Icarus.Models;
using Icarus.Types;
namespace Icarus.Controllers.Managers
{
// NOTE: Do not use metadata for the song's metadata
public class DirectoryManager : BaseManager
{
#region Fields
private Song _song;
private string _rootSongDirectory;
private string _songDirectory;
#endregion
#region Properties
public string SongDirectory
{
get => _songDirectory;
set => _songDirectory = value;
}
#endregion
#region Constructors
public DirectoryManager(IConfiguration config, Song song)
{
_config = config;
_song = song;
Initialize();
}
public DirectoryManager(IConfiguration config)
{
_config = config;
Initialize();
}
public DirectoryManager(string rootDirectory)
{
_rootSongDirectory = rootDirectory;
}
#endregion
#region Methods
public void CreateDirectory()
{
CreateDirectory(_song);
}
public void CreateDirectory(Song song)
{
_song = song;
try
{
_songDirectory = AlbumDirectory();
if (!Directory.Exists(_songDirectory))
{
Directory.CreateDirectory(_songDirectory);
Console.WriteLine($"The directory has been created");
}
Console.WriteLine($"The song will be saved in the following" +
$" directory: {_songDirectory}");
}
catch (Exception ex)
{
var msg = ex.Message;
_logger.Error(msg, "An error occurred");
}
}
public void DeleteEmptyDirectories()
{
try
{
var albumDirectory = AlbumDirectory();
var artistDirectory = ArtistDirectory();
if (IsDirectoryEmpty(albumDirectory))
{
Directory.Delete(albumDirectory);
Console.WriteLine($"directory {albumDirectory} deleted");
}
if (IsDirectoryEmpty(artistDirectory))
{
Directory.Delete(artistDirectory);
Console.WriteLine($"directory {artistDirectory} deleted");
}
}
catch (Exception ex)
{
var exMsg = ex.Message;
Console.WriteLine($"An error occurred {exMsg}");
}
}
public void DeleteEmptyDirectories(Song song)
{
try
{
var albumDirectory = AlbumDirectory(song);
var artistDirectory = ArtistDirectory(song);
if (IsDirectoryEmpty(albumDirectory))
{
Directory.Delete(albumDirectory);
_logger.Info("Album directory deleted");
}
if (IsDirectoryEmpty(artistDirectory))
{
Directory.Delete(artistDirectory);
_logger.Info("Artist directory deleted");
}
}
catch (Exception ex)
{
var msg = ex.Message;
_logger.Error(msg, "An error occurred");
}
}
public string RetrieveAlbumPath(Song song)
{
_logger.Info("Retrieving album song path");
var albumPath = string.Empty;
albumPath = AlbumDirectory(song);
return albumPath;
}
public string RetrieveArtistPath(Song song)
{
_logger.Info("Retrieving artist path");
var artistPath = string.Empty;
artistPath = ArtistDirectory(song);
return artistPath;
}
public string GenerateSongPath(Song song)
{
_logger.Info("Generating song path");
var songPath = string.Empty;
var artistPath = ArtistDirectory(song);
var albumPath = AlbumDirectory(song);
if (!Directory.Exists(artistPath))
{
_logger.Info("Artist path does not exist");
Directory.CreateDirectory(artistPath);
_logger.Info("Creating artist path");
}
if (!Directory.Exists(albumPath))
{
_logger.Info("Album path does not exist");
Directory.CreateDirectory(albumPath);
_logger.Info("Created album path");
}
songPath = albumPath;
return songPath;
}
private void Initialize(DirectoryType dirTypes = DirectoryType.Music)
{
switch (dirTypes)
{
case DirectoryType.Music:
_rootSongDirectory = _config.GetValue<string>("RootMusicPath");
break;
case DirectoryType.CoverArt:
_rootSongDirectory = _config.GetValue<string>("CoverArtPath");
break;
}
}
private bool IsDirectoryEmpty(string path)
{
return !Directory.EnumerateFileSystemEntries(path).Any();
}
private string AlbumDirectory()
{
return AlbumDirectory(_song);
}
private string AlbumDirectory(Song song)
{
var directory = ArtistDirectory(song);
var segment = SerializeValue(song.AlbumTitle);
directory += $@"{segment}/";
Console.WriteLine($"Album directory {directory}");
return directory;
}
private string ArtistDirectory()
{
return ArtistDirectory(_song);
}
private string ArtistDirectory(Song song)
{
var directory = _rootSongDirectory;
var segment = SerializeValue(song.Artist);
directory += $@"{segment}/";
Console.WriteLine($"Artist directory {directory}");
return directory;
}
private string SerializeValue(string value)
{
const int length = 15;
const string chars = "ABCDEF0123456789";
var random = new Random();
var output = new string(Enumerable.Repeat(chars, length).Select(s =>
s[random.Next(s.Length)]).ToArray());
return output;
}
#endregion
}
}
+137
View File
@@ -0,0 +1,137 @@
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using Microsoft.Extensions.Configuration;
using Icarus.Controllers.Utilities;
using Icarus.Models;
using Icarus.Database.Contexts;
namespace Icarus.Controllers.Managers
{
public class GenreManager : BaseManager
{
#region Fields
private GenreContext _genreContext;
#endregion
#region Properties
#endregion
#region Constructors
public GenreManager(IConfiguration config)
{
_config = config;
_connectionString = _config.GetConnectionString("DefaultConnection");
_genreContext = new GenreContext(_connectionString);
}
#endregion
#region Methods
public void SaveGenreToDatabase(ref Song song)
{
_logger.Info("Starting process to save the genre record of the song to the database");
var genre = new Genre
{
GenreName = song.Genre,
SongCount = 1
};
var genreName = song.Genre;
var genreRetrieved = _genreContext.Genres.FirstOrDefault(gnr => gnr.GenreName.Equals(genreName));
if (genreRetrieved == null)
{
_genreContext.Add(genre);
_genreContext.SaveChanges();
}
else
{
genre.GenreID = genreRetrieved.GenreID;
}
song.GenreID = genre.GenreID;
}
public Genre UpdateGenreInDatabase(Song oldSongRecord, Song newSongRecord)
{
var oldGenreRecord = _genreContext.Genres.FirstOrDefault(gnr => gnr.GenreName.Equals(oldSongRecord.Genre));
var oldGenreName = oldGenreRecord.GenreName;
var newGenreName = newSongRecord.Genre;
if (string.IsNullOrEmpty(newGenreName) || oldGenreName.Equals(newGenreName))
{
_logger.Info("No change to the song's Genre");
return oldGenreRecord;
}
_logger.Info("Change to the song's genre found");
if (oldGenreRecord.SongCount <= 1)
{
_logger.Info("Deleting genre record");
_genreContext.Remove(oldGenreRecord);
_genreContext.SaveChanges();
}
if (!(_genreContext.Genres.FirstOrDefault(gnr => gnr.GenreName.Equals(oldSongRecord.Genre)) != null))
{
_logger.Info("Creating new genre record");
var newGenreRecord = new Genre
{
GenreName = newGenreName
};
_genreContext.Add(newGenreRecord);
_genreContext.SaveChanges();
return newGenreRecord;
}
else
{
_logger.Info("Updating existing genre record");
var existingGenreRecord = _genreContext.Genres.FirstOrDefault(gnr => gnr.GenreName.Equals(oldGenreRecord.GenreName));
_genreContext.Update(existingGenreRecord);
_genreContext.SaveChanges();
return existingGenreRecord;
}
}
public void DeleteGenreFromDatabase(Song song)
{
if (!(_genreContext.Genres.FirstOrDefault(gnr => gnr.GenreName.Equals(song.Genre)) != null))
{
_logger.Info("Cannot delete the genre record because it does not exist");
return;
}
var genre = _genreContext.Genres.FirstOrDefault(gnr => gnr.GenreName.Equals(song.Genre));
if (SongsInGenre(genre) <= 1)
{
_genreContext.Remove(genre);
_genreContext.SaveChanges();
}
}
private int SongsInGenre(Genre genre)
{
var sngContext = new SongContext(_connectionString);
var songs = sngContext.Songs.Where(sng => sng.GenreID == genre.GenreID).ToList();
return songs.Count;
}
#endregion
}
}
+474
View File
@@ -0,0 +1,474 @@
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Data;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using NLog;
using Icarus.Controllers.Utilities;
using Icarus.Models;
using Icarus.Database.Contexts;
namespace Icarus.Controllers.Managers
{
public class SongManager : BaseManager
{
#region Fields
private string _tempDirectoryRoot;
private string _archiveDirectoryRoot;
private string _compressedSongFilename;
private string _message;
private SongContext _songContext;
#endregion
#region Properties
public string ArchiveDirectoryRoot
{
get => _archiveDirectoryRoot;
set => _archiveDirectoryRoot = value;
}
public string CompressedSongFilename
{
get => _compressedSongFilename;
set => _compressedSongFilename = value;
}
public string Message
{
get => _message;
set => _message = value;
}
#endregion
#region Constructors
public SongManager(IConfiguration config)
{
_config = config;
Initialize();
}
public SongManager(IConfiguration config, string tempDirectoryRoot)
{
_config = config;
_tempDirectoryRoot = tempDirectoryRoot;
Initialize();
}
#endregion
#region Methods
public SongResult UpdateSong(Song song)
{
var result = new SongResult();
if (!DoesSongExist(song))
{
result.SongTitle = song.Title;
result.Message = "Song does not exist";
return result;
}
try
{
var oldSongRecord = _songContext.RetrieveRecord(song);
song.Filename = oldSongRecord.Filename;
song.SongDirectory = oldSongRecord.SongDirectory;
MetadataRetriever updateMetadata = new MetadataRetriever();
updateMetadata.UpdateMetadata(song, oldSongRecord);
var updatedSong = updateMetadata.UpdatedSongRecord;
var albMgr = new AlbumManager(_config);
var gnrMgr = new GenreManager(_config);
var artMgr = new ArtistManager(_config);
var updatedAlbum = albMgr.UpdateAlbumInDatabase(oldSongRecord, updatedSong);
oldSongRecord.AlbumID = updatedAlbum.AlbumID;
var updatedArtist = artMgr.UpdateArtistInDatabase(oldSongRecord, updatedSong);
oldSongRecord.ArtistID = updatedArtist.ArtistID;
var updatedGenre = gnrMgr.UpdateGenreInDatabase(oldSongRecord, updatedSong);
oldSongRecord.GenreID = updatedGenre.GenreID;
UpdateSongInDatabase(ref oldSongRecord, ref updatedSong, ref result);
DeleteEmptyDirectories(ref oldSongRecord, ref updatedSong);
}
catch (Exception ex)
{
var msg = ex.Message;
_logger.Error(msg, "An error occurred");
result.Message = $"An error occurred: {msg}";
result.SongTitle = song.Title;
}
return result;
}
public bool DeleteSongFromFileSystem(Song songMetaData)
{
bool successful = false;
try
{
var songPath = songMetaData.SongPath();
System.IO.File.Delete(songPath);
successful = true;
DirectoryManager dirMgr = new DirectoryManager(_config, songMetaData);
dirMgr.DeleteEmptyDirectories();
Console.WriteLine("Song successfully deleted");
}
catch (Exception ex)
{
var exMsg = ex.Message;
}
return successful;
}
public bool DoesSongExist(Song song)
{
if (!_songContext.DoesRecordExist(song))
{
return false;
}
return true;
}
public void DeleteSong(Song song)
{
try
{
if (DeleteSongFromFilesystem(song))
{
_logger.Error("Failed to delete the song");
throw new Exception("Failed to delete the song");
}
_logger.Info("Song deleted from the filesystem");
var coverMgr = new CoverArtManager(_config);
var coverArt = coverMgr.GetCoverArt(song);
coverMgr.DeleteCoverArt(coverArt);
coverMgr.DeleteCoverArtFromDatabase(coverArt);
DeleteSongFromDatabase(song);
}
catch (Exception ex)
{
var msg = ex.Message;
_logger.Error(msg, "An error occurred");
}
}
public async Task SaveSongToFileSystem(IFormFile songFile)
{
try
{
_logger.Info("Starting the process of saving the song to the filesystem");
var song = await SaveSongTemp(songFile);
DirectoryManager dirMgr = new DirectoryManager(_config, song);
dirMgr.CreateDirectory();
var filePath = dirMgr.SongDirectory;
filePath += $"{song.Filename}";
_logger.Info($"Absolute song path: {filePath}");
using (var fileStream = new FileStream(filePath, FileMode.Create))
{
var songBytes = await System.IO.File.ReadAllBytesAsync(song.SongPath());
System.IO.File.WriteAllBytesAsync(filePath, songBytes);
System.IO.File.Delete(song.SongPath());
song.SongDirectory = dirMgr.SongDirectory;
_logger.Info("Song successfully saved to filesystem");
}
var coverMgr = new CoverArtManager(_config);
var coverArt = coverMgr.SaveCoverArt(song);
coverMgr.SaveCoverArtToDatabase(ref song, ref coverArt);
SaveSongToDatabase(song);
}
catch (Exception ex)
{
var msg = ex.Message;
_logger.Error(msg, "An error occurred");
}
}
public async Task SaveSongToFileSystem(IFormFile songFile, IFormFile coverArtData, Song song)
{
if (string.IsNullOrEmpty(song.Filename))
{
song.Filename = song.GenerateFilename(1);
}
song.SongDirectory = _tempDirectoryRoot;
song.DateCreated = DateTime.Now;
using (var filestream = new FileStream(song.SongPath(), FileMode.Create))
{
_logger.Info("Saving song to temporary directory");
await songFile.CopyToAsync(filestream);
}
var coverMgr = new CoverArtManager(_config);
var coverArt = coverMgr.SaveCoverArt(coverArtData, song);
var meta = new MetadataRetriever();
song.Duration = meta.RetrieveSongDuration(song.SongPath());
meta.UpdateMetadata(song, song);
meta.UpdateCoverArt(song, coverArt);
DirectoryManager dirMgr = new DirectoryManager(_config, song);
dirMgr.CreateDirectory();
var filePath = dirMgr.SongDirectory + song.Filename;
_logger.Info($"Absolute song path: {filePath}");
using (var fileStream = new FileStream(filePath, FileMode.Create))
{
var songBytes = await System.IO.File.ReadAllBytesAsync(song.SongPath());
await System.IO.File.WriteAllBytesAsync(filePath, songBytes);
System.IO.File.Delete(song.SongPath());
song.SongDirectory = dirMgr.SongDirectory;
_logger.Info("Song successfully saved to filesystem");
}
coverMgr.SaveCoverArtToDatabase(ref song, ref coverArt);
SaveSongToDatabase(song);
}
public async Task<SongData> RetrieveSong(Song songMetaData)
{
var song = new SongData();
try
{
Console.WriteLine("Fetching song from filesystem");
song = await RetrieveSongFromFileSystem(songMetaData);
}
catch (Exception ex)
{
var exMsg = ex.Message;
Console.WriteLine($"An error occurred: {exMsg}");
}
return song;
}
private async Task<SongData> RetrieveSongFromFileSystem(Song details)
{
byte[] uncompressedSong = await System.IO.File.ReadAllBytesAsync(details.SongPath());
return new SongData
{
Data = uncompressedSong
};
}
private async Task<Song> SaveSongTemp(IFormFile songFile)
{
var song = new Song();
_logger.Info("Assigning song filename");
song.SongDirectory = _tempDirectoryRoot;
song.Filename = song.GenerateFilename(1);
using (var filestream = new FileStream(song.SongPath(), FileMode.Create))
{
_logger.Info("Saving song to temporary directory");
await songFile.CopyToAsync(filestream);
}
MetadataRetriever meta = new MetadataRetriever();
song = meta.RetrieveMetaData(song.SongPath());
song.SongDirectory = _tempDirectoryRoot;
song.Filename = song.GenerateFilename(1);
song.DateCreated = DateTime.Now;
return song;
}
private bool SongRecordChanged(Song currentSong, Song songUpdates)
{
var currentTitle = currentSong.Title;
var currentArtist = currentSong.Artist;
var currentAlbum = currentSong.AlbumTitle;
var currentGenre = currentSong.Genre;
var currentYear = currentSong.Year;
if (!currentTitle.Equals(songUpdates.Title) || !currentArtist.Equals(songUpdates.Artist) ||
!currentAlbum.Equals(songUpdates.AlbumTitle) ||
!currentGenre.Equals(songUpdates.Genre) || currentYear != songUpdates.Year)
return true;
return false;
}
private void DeleteEmptyDirectories(ref Song oldSong, ref Song updatedSong)
{
DirectoryManager mgr = new DirectoryManager(_config);
_logger.Info("Checking to see if there are any directories to delete");
mgr.DeleteEmptyDirectories(oldSong);
}
private void Initialize()
{
try
{
_connectionString = _config.GetConnectionString("DefaultConnection");
_songContext = new SongContext(_connectionString);
}
catch (Exception ex)
{
Console.WriteLine($"Error Occurred: {ex.Message}");
}
}
private void SaveSongToDatabase(Song song)
{
_logger.Info("Starting process to save the song to the database");
var albumMgr = new AlbumManager(_config);
var artistMgr = new ArtistManager(_config);
var genreMgr = new GenreManager(_config);
albumMgr.SaveAlbumToDatabase(ref song);
artistMgr.SaveArtistToDatabase(ref song);
genreMgr.SaveGenreToDatabase(ref song);
var info = "Saving Song to DB";
_logger.Info(info);
_songContext.Add(song);
_songContext.SaveChanges();
}
private bool DeleteSongFromFilesystem(Song song)
{
var songPath = song.SongPath();
_logger.Info("Deleting song from the filesystem");
try
{
System.IO.File.Delete(songPath);
}
catch(Exception ex)
{
var msg = ex.Message;
_logger.Error(msg, "An error occurred when attempting to delete the song from the filesystem");
return false;
}
return DoesSongExistOnFilesystem(song);
}
private bool DoesSongExistOnFilesystem(Song song)
{
if (!System.IO.File.Exists(song.SongPath()))
{
_logger.Info("Song does not exist on the filesystem");
return false;
}
_logger.Info("Song exists on the filesystem");
return true;
}
private void UpdateSongInDatabase(ref Song oldSongRecord, ref Song newSongRecord, ref SongResult result)
{
var updatedSongRecord = oldSongRecord;
var songContext = new SongContext(_connectionString);
if (!SongRecordChanged(oldSongRecord, newSongRecord))
{
_logger.Info("No change to the song record");
return;
}
_logger.Info("Changes to song record found");
if (!string.IsNullOrEmpty(newSongRecord.Title))
updatedSongRecord.Title = newSongRecord.Title;
if (!string.IsNullOrEmpty(newSongRecord.AlbumTitle))
{
updatedSongRecord.AlbumTitle = newSongRecord.AlbumTitle;
}
if (!string.IsNullOrEmpty(newSongRecord.Artist))
{
updatedSongRecord.Artist = newSongRecord.Artist;
}
if (!string.IsNullOrEmpty(newSongRecord.Genre))
{
updatedSongRecord.Genre = newSongRecord.Genre;
Console.WriteLine("Genre changed");
Console.WriteLine($"{updatedSongRecord.Genre} {newSongRecord.Genre}");
}
if (newSongRecord.Year != null || newSongRecord.Year > 0)
updatedSongRecord.Year = newSongRecord.Year;
_logger.Info("Applied changes to song record");
_logger.Info("Saving song metadata to the database");
songContext.Update(updatedSongRecord);
songContext.SaveChanges();
newSongRecord = updatedSongRecord;
result.Message = "Successfully updated song";
result.SongTitle = updatedSongRecord.Title;
}
private void DeleteSongFromDatabase(Song song)
{
_logger.Info("Starting process to delete records related to the song from the database");
var albumMgr = new AlbumManager(_config);
var artistMgr = new ArtistManager(_config);
var genreMgr = new GenreManager(_config);
albumMgr.DeleteAlbumFromDatabase(song);
artistMgr.DeleteArtistFromDatabase(song);
genreMgr.DeleteGenreFromDatabase(song);
var sngContext = new SongContext(_connectionString);
sngContext.Songs.Remove(song);
sngContext.SaveChanges();
}
#endregion
}
}
+133
View File
@@ -0,0 +1,133 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Configuration;
using Newtonsoft.Json;
using RestSharp;
using Icarus.Models;
namespace Icarus.Controllers.Managers
{
public class TokenManager : BaseManager
{
#region Fields
private string _clientId;
private string _clientSecret;
private string _audience;
private string _grantType;
private string _url;
#endregion
#region Properties
#endregion
#region Constructors
public TokenManager(IConfiguration config)
{
_config = config;
InitializeValues();
}
#endregion
#region Methods
public LoginResult RetrieveLoginResult(User user)
{
_logger.Info("Preparing Auth0 API request");
var client = new RestClient(_url);
var request = new RestRequest("oauth/token", Method.POST);
var tokenRequest = RetrieveTokenRequest();
_logger.Info("Serializing token object into JSON");
var tokenObject = JsonConvert.SerializeObject(tokenRequest);
request.AddParameter("application/json; charset=utf-8",
tokenObject, ParameterType.RequestBody);
request.RequestFormat = DataFormat.Json;
_logger.Info("Sending request");
IRestResponse response = client.Execute(request);
_logger.Info("Response received");
_logger.Info("Deserializing response");
var tokenResult = JsonConvert
.DeserializeObject<Token>(response.Content);
_logger.Info("Response deserialized");
return new LoginResult
{
UserID = user.UserID, Username = user.Username, Token = tokenResult.AccessToken,
TokenType = tokenResult.TokenType, Expiration = tokenResult.Expiration,
Message = "Successfully retrieved token"
};
}
private TokenRequest RetrieveTokenRequest()
{
_logger.Info("Retrieving token object");
return new TokenRequest
{
ClientId = _clientId, ClientSecret = _clientSecret,
Audience = _audience, GrantType = _grantType
};
}
private void InitializeValues()
{
_logger.Info("Analyzing Auth0 information");
_clientId = _config["Auth0:ClientId"];
_clientSecret = _config["Auth0:ClientSecret"];
_audience = _config["Auth0:ApiIdentifier"];
_grantType = "client_credentials";
_url = $"https://{_config["Auth0:Domain"]}";
PrintCredentials();
}
#region Testing Methods
// For testing purposes
private void PrintCredentials()
{
Console.WriteLine("Auth0 credentials:");
Console.WriteLine($"Client Id: {_clientId}");
Console.WriteLine($"Client Secret: {_clientSecret}");
Console.WriteLine($"Audience: {_audience}");
Console.WriteLine($"Url: {_url}");
}
#endregion
#endregion
#region Classes
private class TokenRequest
{
[JsonProperty("client_id")]
public string ClientId { get; set; }
[JsonProperty("client_secret")]
public string ClientSecret { get; set; }
[JsonProperty("audience")]
public string Audience { get; set; }
[JsonProperty("grant_type")]
public string GrantType { get; set; }
}
private class Token
{
[JsonProperty("access_token")]
public string AccessToken { get; set; }
[JsonProperty("expires_in")]
public int Expiration { get; set; }
[JsonProperty("token_type")]
public string TokenType { get; set; }
}
#endregion
}
}
+345
View File
@@ -0,0 +1,345 @@
using System;
using System.Collections.Generic;
using Microsoft.Extensions.Logging;
using NLog;
using TagLib;
using Icarus.Models;
namespace Icarus.Controllers.Utilities
{
public class MetadataRetriever
{
#region Fields
private static NLog.Logger _logger = NLog.Web.NLogBuilder.ConfigureNLog("nlog.config").GetCurrentClassLogger();
private Song _updatedSong;
private string _message;
private string _title;
private string _artist;
private string _album;
private string _genre;
private int _year;
private int _duration;
#endregion
#region Properties
public Song UpdatedSongRecord
{
get => _updatedSong;
set => _updatedSong = value;
}
public string Message
{
get => _message;
set => _message = value;
}
#endregion
#region Constructors
#endregion
#region Methods
public static void PrintMetadata(Song song)
{
Console.WriteLine("\n\nMetadata of the song:");
Console.WriteLine($"ID: {song.SongID}");
Console.WriteLine($"Title: {song.Title}");
Console.WriteLine($"Artist: {song.Artist}");
Console.WriteLine($"Album: {song.AlbumTitle}");
Console.WriteLine($"Genre: {song.Genre}");
Console.WriteLine($"Year: {song.Year}");
Console.WriteLine($"Duration: {song.Duration}");
Console.WriteLine($"AlbumID: {song.AlbumID}");
Console.WriteLine($"ArtistID: {song.ArtistID}");
Console.WriteLine($"GenreID: {song.GenreID}");
Console.WriteLine($"Song Path: {song.SongPath()}");
Console.WriteLine($"Filename: {song.Filename}");
Console.WriteLine("\n");
_logger.Info("Metadata of the song");
_logger.Info($"Title: {song.Title}");
_logger.Info($"Artist: {song.Artist}");
_logger.Info($"Album: {song.AlbumTitle}");
_logger.Info($"Genre: {song.Genre}");
_logger.Info($"Year: {song.Year}");
_logger.Info($"Duration: {song.Duration}");
}
public Song RetrieveMetaData(string filePath)
{
Song song = new Song();
try
{
TagLib.File fileTag = TagLib.File.Create(filePath);
_title = fileTag.Tag.Title;
_artist = string.Join("", fileTag.Tag.Performers);
_album = fileTag.Tag.Album;
_genre = string.Join("", fileTag.Tag.Genres);
_year = (int)fileTag.Tag.Year;
_duration = (int)fileTag.Properties.Duration.TotalSeconds;
var albumArtist = string.Join("", fileTag.Tag.AlbumArtists);
var track = (int)fileTag.Tag.Track;
var disc = (int)fileTag.Tag.Disc;
song.Title = _title;
song.Artist = _artist;
song.AlbumTitle = _album;
song.AlbumArtist = albumArtist;
song.Genre = _genre;
song.Year = _year;
song.Duration = _duration;
song.Track = track;
song.Disc = disc;
song.TrackCount = (int)fileTag.Tag.TrackCount;
song.DiscCount = (int)fileTag.Tag.DiscCount;
}
catch (Exception ex)
{
var msg = ex.Message;
Console.WriteLine("An error occurred in MetadataRetriever");
Console.WriteLine(msg);
_logger.Error(msg, "An error occurred in MetadataRetriever");
}
return song;
}
public int RetrieveSongDuration(string filepath)
{
var duration = 0;
var fileTag = TagLib.File.Create(filepath);
duration = (int)fileTag.Properties.Duration.TotalSeconds;
return duration;
}
public byte[] RetrieveCoverArtBytes(Song song)
{
try
{
Console.WriteLine("Fetching image");
var tag = TagLib.File.Create(song.SongPath());
byte[] imgBytes = tag.Tag.Pictures[0].Data.Data;
return imgBytes;
}
catch (Exception ex)
{
var msg = ex.Message;
_logger.Error(msg, "An error occurred in MetadataRetriever");
}
return null;
}
public void UpdateMetadata(Song updatedSong, Song oldSong)
{
try
{
InitializeUpdatedSong(oldSong);
var songValues = CheckSongValues(updatedSong);
PerformUpdate(updatedSong, songValues);
Message = "Successfully updated metadata";
}
catch (Exception ex)
{
var msg = ex.Message;
Console.WriteLine($"An error occurred: {msg}");
_logger.Error(msg, "An error occurred");
Message = "Failed to update metadata";
}
}
public void UpdateCoverArt(Song song, CoverArt coverArt)
{
Console.WriteLine("Updating song's cover art");
var tag = TagLib.File.Create(song.SongPath());
var pics = tag.Tag.Pictures;
Array.Resize(ref pics, 1);
pics[0] = new Picture(coverArt.ImagePath)
{
Description = "Cover Art"
};
tag.Tag.Pictures = pics;
tag.Save();
}
private void PerformUpdate(Song updatedSong, SortedDictionary<string, bool> checkedValues)
{
var filePath = updatedSong.SongPath();
var title = updatedSong.Title;
var artist = updatedSong.Artist;
var album = updatedSong.AlbumTitle;
var genre = updatedSong.Genre;
var year = updatedSong.Year;
var albumArtist = updatedSong.AlbumArtist;
var track = updatedSong.Track;
var trackCount = updatedSong.TrackCount;
var disc = updatedSong.Disc;
var discCount = updatedSong.DiscCount;
TagLib.File fileTag = TagLib.File.Create(filePath);
try
{
Console.WriteLine($"Updating metadata of {title}");
_logger.Info($"Updating metadata of {title}");
foreach (var key in checkedValues.Keys)
{
bool result = checkedValues[key];
if (!result)
switch (key.ToLower())
{
case "title":
_updatedSong.Title = title;
fileTag.Tag.Title = title;
break;
case "artists":
_updatedSong.Artist = artist;
fileTag.Tag.Performers = new []{artist};
break;
case "album":
_updatedSong.AlbumTitle = album;
fileTag.Tag.Album = album;
break;
case "genre":
_updatedSong.Genre = genre;
fileTag.Tag.Genres = new []{genre};
break;
case "year":
_updatedSong.Year = year;
fileTag.Tag.Year = (uint)year;
break;
case "albumartist":
_updatedSong.AlbumArtist = albumArtist;
fileTag.Tag.AlbumArtists = new []{albumArtist};
break;
case "track":
_updatedSong.Track = track;
fileTag.Tag.Track = (uint)(track);
break;
case "trackcount":
_updatedSong.TrackCount = trackCount;
fileTag.Tag.TrackCount = (uint)(trackCount);
break;
case "disc":
_updatedSong.Disc = disc;
fileTag.Tag.Disc = (uint)(disc);
break;
case "disccount":
_updatedSong.DiscCount = discCount;
fileTag.Tag.DiscCount = (uint)(discCount);
break;
}
}
fileTag.Save();
Console.WriteLine("Successfully updated metadata");
_logger.Info("Successfully updated metadata");
}
catch (Exception ex)
{
var msg = ex.Message;
Console.WriteLine($"An error occurred:\n{msg}");
_logger.Error(msg, "An error occurred");
}
}
private void InitializeUpdatedSong(Song song)
{
_updatedSong = song;
}
private void PrintMetadata()
{
Console.WriteLine("\n\nMetadata of the song:");
Console.WriteLine($"Title: {_title}");
Console.WriteLine($"Artist: {_artist}");
Console.WriteLine($"Album: {_album}");
Console.WriteLine($"Genre: {_genre}");
Console.WriteLine($"Year: {_year}");
Console.WriteLine($"Duration: {_duration}\n\n");
_logger.Info("Metadata of the song");
_logger.Info($"Title: {_title}");
_logger.Info($"Artist: {_artist}");
_logger.Info($"Album: {_album}");
_logger.Info($"Genre: {_genre}");
_logger.Info($"Year: {_year}");
_logger.Info($"Duration: {_duration}");
}
private void PrintMetadata(Song song, string message)
{
Console.WriteLine($"\n\n{message}");
Console.WriteLine($"Title: {song.Title}");
Console.WriteLine($"Artist: {song.Artist}");
Console.WriteLine($"Album: {song.AlbumTitle}");
Console.WriteLine($"Genre: {song.Genre}");
Console.WriteLine($"Year: {song.Year}");
Console.WriteLine($"Duration: {song.Duration}\n\n");
_logger.Info(message);
_logger.Info($"Title: {_title}");
_logger.Info($"Artist: {_artist}");
_logger.Info($"Album: {_album}");
_logger.Info($"Genre: {_genre}");
_logger.Info($"Year: {_year}");
_logger.Info($"Duration: {_duration}");
}
private SortedDictionary<string, bool> CheckSongValues(Song song)
{
var songValues = new SortedDictionary<string, bool>();
Console.WriteLine("Checking for null data");
_logger.Info("Checking for null data");
try
{
songValues["Title"] = String.IsNullOrEmpty(song.Title);
songValues["Artists"] = String.IsNullOrEmpty(song.Artist);
songValues["Album"] = String.IsNullOrEmpty(song.AlbumTitle);
songValues["Genre"] = String.IsNullOrEmpty(song.Genre);
songValues["AlbumArtist"] = String.IsNullOrEmpty(song.AlbumArtist);
songValues["Year"] = CheckIntField(song.Year);
songValues["Track"] = CheckIntField(song.Track);
songValues["TrackCount"] = CheckIntField(song.TrackCount);
songValues["Disc"] = CheckIntField(song.Disc);
songValues["DiscCount"] = CheckIntField(song.Disc);
Console.WriteLine("Checking for null data completed");
_logger.Info("Checking for null data completed");
}
catch (Exception ex)
{
var msg = ex.Message;
Console.WriteLine($"An error occurred: \n{msg}");
_logger.Error(msg, "An error occurred");
}
return songValues;
}
private bool CheckIntField(int? value)
{
if (value == null)
{
return true;
}
else if (value == 0)
{
return true;
}
return false;
}
#endregion
}
}
@@ -0,0 +1,90 @@
using System;
using System.Security.Cryptography;
using BCrypt.Net;
using Microsoft.AspNetCore.Cryptography.KeyDerivation;
using NLog;
using Icarus.Models;
namespace Icarus.Controllers.Utilities
{
public class PasswordEncryption
{
#region Fields
private static Logger _logger = NLog.LogManager.GetCurrentClassLogger();
#endregion
#region Properties
#endregion
#region Constructor
#endregion
#region Methods
public bool VerifyPassword(User user, string password)
{
try
{
var result = BCrypt.Net.BCrypt.Verify(password, user.Password);
return result;
}
catch (Exception ex)
{
var msg = ex.Message;
_logger.Error(msg, "An error occurred");
}
return false;
}
public string HashPassword(User user)
{
try
{
string hashedPassword = string.Empty;
hashedPassword = BCrypt.Net.BCrypt.HashPassword(user.Password);
_logger.Info("Successfully hashed password");
return hashedPassword;
}
catch (Exception ex)
{
var exMsg = ex.Message;
_logger.Error(exMsg, "An error occurred");
}
return null;
}
string GenerateHash(string password, byte[] salt)
{
string hashed = Convert.ToBase64String(KeyDerivation.Pbkdf2(
password: password, salt: salt,
prf: KeyDerivationPrf.HMACSHA1,
iterationCount: 10000,
numBytesRequested: 256/8));
return hashed;
}
byte[] GenerateSalt()
{
byte[] salt = new byte[128/8];
using (var rng = RandomNumberGenerator.Create())
rng.GetBytes(salt);
return salt;
}
#endregion
}
}
+130
View File
@@ -0,0 +1,130 @@
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Ionic.Zip;
using Icarus.Models;
namespace Icarus.Controllers.Utilities
{
public class SongCompression
{
#region Fields
string _compressedSongFilename;
string _tempDirectory;
byte[] _uncompressedSong;
#endregion
#region Propterties
public string CompressedSongFilename
{
get => _compressedSongFilename;
set => _compressedSongFilename = value;
}
#endregion
#region Constructors
public SongCompression()
{
}
public SongCompression(string tempDirectory)
{
_tempDirectory = tempDirectory;
}
public SongCompression(byte[] uncompressedSong)
{
_uncompressedSong = uncompressedSong;
}
#endregion
#region Methods
public async Task<SongData> RetrieveCompressedSong(Song song)
{
SongData songData = new SongData();
try
{
var archivePath = RetrieveCompressesSongPath(song);
Console.WriteLine($"Compressed song saved to: {archivePath}");
songData.Data = await System.IO.File.ReadAllBytesAsync(archivePath);
}
catch(Exception ex)
{
var exMsg = ex.Message;
Console.WriteLine($"An error ocurred: \n{exMsg}");
}
return songData;
}
public string RetrieveCompressesSongPath(Song songDetails)
{
string tmpZipFilePath = _tempDirectory + songDetails.Filename;
try
{
using (ZipFile zip = new ZipFile())
{
zip.AddFile(songDetails.SongPath());
zip.Save(tmpZipFilePath);
}
Console.WriteLine("Successfully compressed");
}
catch (Exception ex)
{
var exMsg = ex.Message;
Console.WriteLine("An error ocurred");
Console.WriteLine(exMsg);
}
if (songDetails.Filename.Contains(".mp3"))
{
_compressedSongFilename = StripMP3Extension(songDetails.Filename);
}
return tmpZipFilePath;
}
// Method not being used
public byte[] CompressedSong(byte[] uncompressedSong)
{
byte[] compressedSong = null;
try
{
Console.WriteLine("Song has been successfully compressed");
}
catch (Exception ex)
{
var exMsg = ex.Message;
Console.WriteLine("An error ocurred:");
Console.WriteLine(exMsg);
}
return compressedSong;
}
string StripMP3Extension(string filename)
{
Console.WriteLine($"Before: {filename}");
int filenameLength = filename.Length;
Console.WriteLine($"Filename length {filenameLength}");
var endIndex = filenameLength - 1;
var startIndex = endIndex - 3;
Console.WriteLine($"Starting index {startIndex} and ending index {endIndex}");
var stripped = filename.Remove(startIndex, 4);
stripped += ".zip";
Console.WriteLine($"After {stripped}");
return stripped;
}
#endregion
}
}
+81
View File
@@ -0,0 +1,81 @@
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Icarus.Models;
using Icarus.Database.Contexts;
// using Icarus.Database.Repositories;
namespace Icarus.Controllers.V1
{
[Route("api/v1/album")]
[ApiController]
public class AlbumController : ControllerBase
{
#region Fields
private readonly ILogger<AlbumController> _logger;
private string _connectionString;
private IConfiguration _config;
#endregion
#region Properties
#endregion
#region Constructors
public AlbumController(ILogger<AlbumController> logger, IConfiguration config)
{
_logger = logger;
_config = config;
_connectionString = _config.GetConnectionString("DefaultConnection");
}
#endregion
#region HTTP Routes
[HttpGet]
[Authorize("read:albums")]
public IActionResult Get()
{
List<Album> albums = new List<Album>();
var albumContext = new AlbumContext(_connectionString);
albums = albumContext.Albums.ToList();
if (albums.Count > 0)
return Ok(albums);
else
return NotFound();
}
[HttpGet("{id}")]
[Authorize("read:albums")]
public IActionResult Get(int id)
{
Album album = new Album
{
AlbumID = id
};
var albumContext = new AlbumContext(_connectionString);
if (albumContext.DoesRecordExist(album))
{
album = albumContext.RetrieveRecord(album);
return Ok(album);
}
else
return NotFound();
}
#endregion
}
}
+78
View File
@@ -0,0 +1,78 @@
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Icarus.Models;
using Icarus.Database.Contexts;
namespace Icarus.Controllers.V1
{
[Route("api/v1/artist")]
[ApiController]
public class ArtistController : ControllerBase
{
#region Fields
private readonly ILogger<ArtistController> _logger;
private string _connectionString;
private IConfiguration _config;
#endregion
#region Properties
#endregion
#region Constructors
public ArtistController(ILogger<ArtistController> logger, IConfiguration config)
{
_logger = logger;
_config = config;
_connectionString = _config.GetConnectionString("DefaultConnection");
}
#endregion
#region HTTP Routes
[HttpGet]
[Authorize("read:artists")]
public IActionResult Get()
{
var artistContext = new ArtistContext(_connectionString);
var artists = artistContext.Artists.ToList();
if (artists.Count > 0)
return Ok(artists);
else
return NotFound();
}
[HttpGet("{id}")]
[Authorize("read:artists")]
public IActionResult Get(int id)
{
Artist artist = new Artist
{
ArtistID = id
};
var artistContext = new ArtistContext(_connectionString);
if (artistContext.DoesRecordExist(artist))
{
artist = artistContext.RetrieveRecord(artist);
return Ok(artist);
}
else
return NotFound();
}
#endregion
}
}
+85
View File
@@ -0,0 +1,85 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Icarus.Controllers.Managers;
using Icarus.Database.Contexts;
using Icarus.Models;
namespace Icarus.Controllers.V1
{
[Route("api/v1/coverart")]
[ApiController]
public class CoverArtController : ControllerBase
{
#region Fields
private readonly ILogger<CoverArtController> _logger;
private string _connectionString;
private IConfiguration _config;
#endregion
#region Constructors
public CoverArtController(ILogger<CoverArtController> logger, IConfiguration config)
{
_logger = logger;
_config = config;
_connectionString = _config.GetConnectionString("DefaultConnection");
}
#endregion
#region HTTP Routes
public IActionResult Get()
{
var coverArtContext = new CoverArtContext(_connectionString);
var coverArtRecords = coverArtContext.CoverArtImages.ToList();
if (coverArtRecords == null)
{
_logger.LogInformation("No cover art records");
return NotFound();
}
else
{
_logger.LogInformation("Found cover art records");
return Ok(coverArtRecords);
}
}
[HttpGet("{id}")]
[Authorize("download:cover_art")]
public async Task<IActionResult> Get(int id)
{
var coverArt = new CoverArt { CoverArtID = id };
var coverArtContext = new CoverArtContext(_connectionString);
coverArt = coverArtContext.RetrieveRecord(coverArt);
if (coverArt != null)
{
_logger.LogInformation("Found cover art record");
var coverArtBytes = await System.IO.File.ReadAllBytesAsync(
coverArt.ImagePath);
return File(coverArtBytes, "application/x-msdownload",
coverArt.SongTitle);
}
else
{
_logger.LogInformation("Cover art not found");
return NotFound();
}
}
#endregion
}
}
+79
View File
@@ -0,0 +1,79 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Icarus.Models;
using Icarus.Database.Contexts;
namespace Icarus.Controllers.V1
{
[Route("api/v1/genre")]
[ApiController]
public class GenreController : ControllerBase
{
#region Fields
private readonly ILogger<GenreController> _logger;
private string _connectionString;
private IConfiguration _config;
#endregion
#region Properties
#endregion
#region Constructors
public GenreController(ILogger<GenreController> logger, IConfiguration config)
{
_logger = logger;
_config = config;
_connectionString = _config.GetConnectionString("DefaultConnection");
}
#endregion
#region HTTP Routes
[HttpGet]
[Authorize("read:genre")]
public IActionResult Get()
{
var genres = new List<Genre>();
var genreStore = new GenreContext(_connectionString);
genres = genreStore.Genres.ToList();
if (genres.Count > 0)
return Ok(genres);
else
return NotFound(new List<Genre>());
}
[HttpGet("{id}")]
[Authorize("read:genre")]
public IActionResult Get(int id)
{
var genre = new Genre
{
GenreID = id
};
var genreStore = new GenreContext(_connectionString);
if (genreStore.DoesRecordExist(genre))
{
genre = genreStore.RetrieveRecord(genre);
return Ok(genre);
}
else
return NotFound(new Genre());
}
#endregion
}
}
+89
View File
@@ -0,0 +1,89 @@
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Icarus.Controllers.Managers;
using Icarus.Controllers.Utilities;
using Icarus.Models;
using Icarus.Database.Contexts;
namespace Icarus.Controllers.V1
{
[Route("api/v1/login")]
[ApiController]
public class LoginController : ControllerBase
{
#region Fields
private string _connectionString;
private IConfiguration _config;
private ILogger<LoginController> _logger;
#endregion
#region Properties
#endregion
#region Contructors
public LoginController(IConfiguration config, ILogger<LoginController> logger)
{
_logger = logger;
_config = config;
_connectionString = _config.GetConnectionString("DefaultConnection");
}
#endregion
#region HTTP endpoints
public IActionResult Post([FromBody] User user)
{
var context = new UserContext(_connectionString);
_logger.LogInformation("Starting process of validating credentials");
var message = "Invalid credentials";
var password = user.Password;
var loginRes = new LoginResult
{
Username = user.Username
};
if (context.Users.FirstOrDefault(usr => usr.Username.Equals(user.Username)) != null)
{
user = context.Users.FirstOrDefault(usr => usr.Username.Equals(user.Username));
var validatePass = new PasswordEncryption();
var validated = validatePass.VerifyPassword(user, password);
if (!validated)
{
loginRes.Message = message;
_logger.LogInformation(message);
return Ok(loginRes);
}
_logger.LogInformation("Successfully validated user credentials");
TokenManager tk = new TokenManager(_config);
loginRes = tk.RetrieveLoginResult(user);
return Ok(loginRes);
}
else
{
loginRes.Message = message;
return NotFound(loginRes);
}
}
#endregion
}
}
View File
+85
View File
@@ -0,0 +1,85 @@
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Icarus.Controllers.Managers;
using Icarus.Controllers.Utilities;
using Icarus.Models;
using Icarus.Database.Contexts;
namespace Icarus.Controllers.V1
{
[Route("api/v1/register")]
[ApiController]
public class RegisterController : ControllerBase
{
#region Fields
private string _connectionString;
private IConfiguration _config;
#endregion
#region Properties
#endregion
#region Constructor
public RegisterController(IConfiguration config)
{
_config = config;
_connectionString = _config.GetConnectionString("DefaultConnection");
}
#endregion
[HttpPost]
public IActionResult Post([FromBody] User user)
{
PasswordEncryption pe = new PasswordEncryption();
user.Password = pe.HashPassword(user);
user.EmailVerified = false;
user.Status = "Registered";
user.DateCreated = DateTime.Now;
UserContext context = null;
try
{
context = new UserContext(_config.GetConnectionString("DefaultConnection"));
context.Add(user);
context.SaveChanges();
}
catch (Exception ex)
{
var msg = ex.Message;
var stackTrace = ex.StackTrace;
Console.WriteLine($"An error occurred: {msg}");
}
var registerResult = new RegisterResult
{
Username = user.Username
};
if (context.Users.FirstOrDefault(sng => sng.Username.Equals(user.Username)) != null)
{
registerResult.Message = "Successful registration";
registerResult.SuccessfullyRegistered = true;
return Ok(registerResult);
}
else
{
registerResult.Message = "Registration failed";
registerResult.SuccessfullyRegistered = false;
return Ok(registerResult);
}
}
}
}
@@ -0,0 +1,65 @@
using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Icarus.Controllers.Managers;
using Icarus.Controllers.Utilities;
using Icarus.Models;
using Icarus.Database.Contexts;
namespace Icarus.Controllers.V1
{
[Route("api/v1/song/compressed/data")]
[ApiController]
public class SongCompressedDataController : ControllerBase
{
#region Fields
private string _connectionString;
private IConfiguration _config;
private string _songTempDir;
private string _archiveDir;
#endregion
#region Properties
#endregion
#region Constructor
public SongCompressedDataController(IConfiguration config)
{
_songTempDir = _config.GetValue<string>("TemporaryMusicPath");
_archiveDir = _config.GetValue<string>("ArchivePath");
_config = config;
_connectionString = _config.GetConnectionString("DefaultConnection");
}
#endregion
#region API Routes
[HttpGet("{id}")]
[Authorize("download:songs")]
public async Task<IActionResult> Get(int id)
{
var context = new SongContext(_connectionString);
SongCompression cmp = new SongCompression(_archiveDir);
Console.WriteLine($"Archive directory root: {_archiveDir}");
Console.WriteLine("Starting process of retrieving comrpessed song");
SongData song = await cmp.RetrieveCompressedSong(context.RetrieveRecord(new Song{ SongID = id }));
return File(song.Data, "application/x-msdownload", cmp.CompressedSongFilename);
}
#endregion
}
}
+104
View File
@@ -0,0 +1,104 @@
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Icarus.Controllers.Managers;
using Icarus.Controllers.Utilities;
using Icarus.Models;
using Icarus.Database.Contexts;
namespace Icarus.Controllers.V1
{
[Route("api/v1/song")]
[ApiController]
public class SongController : ControllerBase
{
#region Fields
private readonly ILogger<SongController> _logger;
private string _connectionString;
private IConfiguration _config;
private SongManager _songMgr;
#endregion
#region Properties
#endregion
#region Constructor
public SongController(IConfiguration config, ILogger<SongController> logger)
{
_config = config;
_connectionString = _config.GetConnectionString("DefaultConnection");
_logger = logger;
_songMgr = new SongManager(config);
}
#endregion
[HttpGet]
[Authorize("read:song_details")]
public IActionResult Get()
{
List<Song> songs = new List<Song>();
Console.WriteLine("Attemtping to retrieve songs");
_logger.LogInformation("Attempting to retrieve songs");
var context = new SongContext(_connectionString);
songs = context.Songs.ToList();
if (songs.Count > 0)
return Ok(songs);
else
return NotFound();
}
[HttpGet("{id}")]
[Authorize("read:song_details")]
public IActionResult Get(int id)
{
var context = new SongContext(_connectionString);
Song song = new Song { SongID = id };
song = context.RetrieveRecord(song);
Console.WriteLine("Here");
if (song.SongID != 0)
return Ok(song);
else
return NotFound();
}
[Authorize("update:songs")]
[HttpPut("{id}")]
public IActionResult Put(int id, [FromBody] Song song)
{
var context = new SongContext(_connectionString);
song.SongID = id;
Console.WriteLine("Retrieving filepath of song");
_logger.LogInformation("Retrieving filepath of song");
if (!_songMgr.DoesSongExist(song))
{
return NotFound(new SongResult
{
Message = "Song does not exist"
});
}
var songRes = _songMgr.UpdateSong(song);
return Ok(songRes);
}
}
}
+174
View File
@@ -0,0 +1,174 @@
using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Icarus.Controllers.Managers;
using Icarus.Models;
using Icarus.Database.Contexts;
namespace Icarus.Controllers.V1
{
[Route("api/v1/song/data")]
[ApiController]
public class SongDataController : ControllerBase
{
#region Fields
private string _connectionString;
private IConfiguration _config;
private ILogger<SongDataController> _logger;
private SongManager _songMgr;
private string _songTempDir;
#endregion
#region Properties
#endregion
#region Constructor
public SongDataController(IConfiguration config, ILogger<SongDataController> logger)
{
_config = config;
_connectionString = _config.GetConnectionString("DefaultConnection");
_logger = logger;
_songTempDir = _config.GetValue<string>("TemporaryMusicPath");
_songMgr = new SongManager(config, _songTempDir);
}
#endregion
[HttpGet("download/{id}")]
[Route("private-scoped")]
[Authorize("download:songs")]
public async Task<IActionResult> Get(int id)
{
var songContext = new SongContext(_connectionString);
var songMetaData = songContext.RetrieveRecord(new Song { SongID = id});
var song = await _songMgr.RetrieveSong(songMetaData);
return File(song.Data, "application/x-msdownload", songMetaData.Filename);
}
// Assumes that the song already has metadata such as
// Title
// Artist
// Album
// Genre
// Year
// Track
// Track count
// Disc
// Disc count
// Cover art
//
[HttpPost("upload"), DisableRequestSizeLimit]
[Route("private-scoped")]
[Authorize("upload:songs")]
public async Task<IActionResult> Post([FromForm(Name = "file")] List<IFormFile> songData)
{
try
{
Console.WriteLine("Uploading song...");
_logger.LogInformation("Uploading song...");
var uploads = _songTempDir;
Console.WriteLine($"Song Root Path {uploads}");
_logger.LogInformation($"Song root path {uploads}");
foreach (var sng in songData)
if (sng.Length > 0) {
Console.WriteLine($"Song filename {sng.FileName}");
_logger.LogInformation($"Song filename {sng.FileName}");
await _songMgr.SaveSongToFileSystem(sng);
}
return Ok();
}
catch (Exception ex)
{
var msg = ex.Message;
_logger.LogError(msg, "An error occurred");
}
return NotFound();
}
// The client is expected to send the file, metadata, and cover art separately.
// Any metadata already on the file will be overwritten with values from the metadata
// as well as the cover art
//
[HttpPost("upload/with/data")]
[Route("private-scoped")]
[Authorize("upload:songs")]
public async Task<IActionResult> Post ([FromForm] UploadSongWithDataForm up)
{
try
{
if (up.SongData.Length > 0 && up.CoverArtData.Length > 0 && !string.IsNullOrEmpty(up.SongFile))
{
var song = Newtonsoft.Json.JsonConvert.DeserializeObject<Song>(up.SongFile);
_logger.LogInformation($"Song title: {song.Title}");
await _songMgr.SaveSongToFileSystem(up.SongData, up.CoverArtData, song);
}
}
catch (Exception ex)
{
_logger.LogError(ex.Message, "An error occurred");
}
return Ok();
}
[HttpDelete("delete/{id}")]
[Authorize("delete:songs")]
public IActionResult Delete(int id)
{
var songContext = new SongContext(_connectionString);
var songMetaData = new Song{ SongID = id };
Console.WriteLine($"Id {songMetaData.SongID}");
songMetaData = songContext.RetrieveRecord(songMetaData);
if (string.IsNullOrEmpty(songMetaData.Title))
{
_logger.LogInformation("Song does not exist");
return NotFound("Song does not exist");
}
else
{
_logger.LogInformation("Starting process of deleting song from the filesystem and database");
_songMgr.DeleteSong(songMetaData);
return Ok();
}
}
public class UploadSongWithDataForm
{
[FromForm(Name = "file")]
public IFormFile SongData { get; set; }
// TODO: Think about making this optional and if it is not provided, use the stock cover art
[FromForm(Name = "cover")]
public IFormFile CoverArtData { get; set; }
[FromForm(Name = "metadata")]
public string SongFile { get; set; }
}
}
}
+69
View File
@@ -0,0 +1,69 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http.Headers;
using System.Web;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Icarus.Models;
using Icarus.Database.Contexts;
namespace Icarus.Controllers.V1
{
[Route("api/v1/song/stream")]
[ApiController]
public class SongStreamController : ControllerBase
{
#region Fields
private ILogger<SongStreamController> _logger;
private string _connectionString;
private IConfiguration _config;
#endregion
#region Properties
#endregion
#region Constructor
public SongStreamController(ILogger<SongStreamController> logger, IConfiguration config)
{
_logger = logger;
_config = config;
_connectionString = _config.GetConnectionString("DefaultConnection");
}
#endregion
#region HTTP endpoints
[HttpGet("{id}")]
[Authorize("stream:songs")]
public async Task<IActionResult> Get(int id)
{
var context = new SongContext(_config.GetConnectionString("DefaultConnection"));
var song = context.Songs.FirstOrDefault(sng => sng.SongID == id);
var stream = new FileStream(song.SongPath(), FileMode.Open, FileAccess.Read);
stream.Position = 0;
var filename = $"{song.Title}.mp3";
_logger.LogInformation("Starting to stream song...>");
Console.WriteLine("Starting to streamsong...");
var file = await Task.Run(() => {
return File(stream, "application/octet-stream", filename);
});
return file;
}
#endregion
}
}
+37
View File
@@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Icarus.Models;
namespace Icarus.Database.Contexts
{
public class AlbumContext : DbContext
{
public DbSet<Album> Albums { get; set; }
public AlbumContext(DbContextOptions<AlbumContext> options) : base(options) { }
public AlbumContext(string connString) : base(new DbContextOptionsBuilder<AlbumContext>()
.UseMySQL(connString).Options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Album>()
.ToTable("Album");
}
public Album RetrieveRecord(Album album)
{
return Albums.FirstOrDefault(alb => alb.AlbumID == album.AlbumID);
}
public bool DoesRecordExist(Album album)
{
return Albums.FirstOrDefault(alb => alb.AlbumID == album.AlbumID) != null ? true : false;
}
}
}
+39
View File
@@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Icarus.Models;
namespace Icarus.Database.Contexts
{
public class ArtistContext : DbContext
{
public DbSet<Artist> Artists { get; set; }
public ArtistContext(DbContextOptions<ArtistContext> options) : base (options) { }
public ArtistContext(string connString) : base(new DbContextOptionsBuilder<ArtistContext>()
.UseMySQL(connString).Options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Artist>()
.ToTable("Artist");
}
public Artist RetrieveRecord(Artist artist)
{
return Artists.FirstOrDefault(arst => arst.ArtistID == artist.ArtistID);
}
public bool DoesRecordExist(Artist artist)
{
return Artists.FirstOrDefault(arst => arst.ArtistID == artist.ArtistID) != null ? true : false;
}
}
}
+37
View File
@@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Icarus.Models;
namespace Icarus.Database.Contexts
{
public class CoverArtContext : DbContext
{
public DbSet<CoverArt> CoverArtImages { get; set; }
public CoverArtContext(DbContextOptions<CoverArtContext> options) : base(options) { }
public CoverArtContext(string connString) : base(new DbContextOptionsBuilder<CoverArtContext>()
.UseMySQL(connString).Options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<CoverArt>()
.ToTable("CoverArt");
}
public CoverArt RetrieveRecord(CoverArt cover)
{
return CoverArtImages.FirstOrDefault(cov => cov.CoverArtID == cover.CoverArtID);
}
public bool DoesRecordExist(CoverArt cover)
{
return CoverArtImages.FirstOrDefault(cov => cov.CoverArtID == cover.CoverArtID) != null ? true : false;
}
}
}
+37
View File
@@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Icarus.Models;
namespace Icarus.Database.Contexts
{
public class GenreContext : DbContext
{
public DbSet<Genre> Genres { get; set; }
public GenreContext(DbContextOptions<GenreContext> options) : base(options) { }
public GenreContext(string connString) : base(new DbContextOptionsBuilder<GenreContext>()
.UseMySQL(connString).Options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Genre>()
.ToTable("Genre");
}
public Genre RetrieveRecord(Genre genre)
{
return Genres.FirstOrDefault(gnr => gnr.GenreID == genre.GenreID);
}
public bool DoesRecordExist(Genre genre)
{
return Genres.FirstOrDefault(gnr => gnr.GenreID == genre.GenreID) != null ? true : false;
}
}
}
+89
View File
@@ -0,0 +1,89 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Icarus.Models;
namespace Icarus.Database.Contexts
{
public class SongContext : DbContext
{
public DbSet<Song> Songs { get; set; }
public SongContext(string connString) : base(new DbContextOptionsBuilder<SongContext>()
.UseMySQL(connString).Options)
{
}
public SongContext(DbContextOptions<SongContext> options) : base(options) { }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Song>()
.ToTable("Song");
/**
modelBuilder.Entity<Song>()
.HasOne(s => s.Album)
.WithMany(al => al.Songs)
.HasForeignKey(s => s.AlbumID)
.OnDelete(DeleteBehavior.SetNull);
modelBuilder.Entity<Song>()
.HasOne(sa => sa.SongArtist)
.WithMany(ar => ar.Songs)
.HasForeignKey(s => s.ArtistID)
.OnDelete(DeleteBehavior.SetNull);
modelBuilder.Entity<Song>()
.HasOne(s => s.SongGenre)
.WithMany(gnr => gnr.Songs)
.HasForeignKey(s => s.GenreID)
.OnDelete(DeleteBehavior.SetNull);
modelBuilder.Entity<Song>()
.HasOne(s => s.SongYear)
.WithMany(yr => yr.Songs)
.HasForeignKey(s => s.YearID)
.OnDelete(DeleteBehavior.SetNull);
modelBuilder.Entity<Song>()
.HasOne(s => s.SongCoverArt)
.WithMany(ca => ca.Songs)
.HasForeignKey(s => s.CoverArtID)
.OnDelete(DeleteBehavior.SetNull);
*/
modelBuilder.Entity<Song>()
.Property(s => s.Year)
.IsRequired(false);
modelBuilder.Entity<Song>()
.Property(s => s.GenreID)
.IsRequired(false);
modelBuilder.Entity<Song>()
.Property(s => s.ArtistID)
.IsRequired(false);
modelBuilder.Entity<Song>()
.Property(s => s.AlbumID)
.IsRequired(false);
modelBuilder.Entity<Song>()
.Property(s => s.CoverArtID)
.IsRequired(false);
}
public Song RetrieveRecord(Song song)
{
return Songs.FirstOrDefault(sng => sng.SongID == song.SongID);
}
public bool DoesRecordExist(Song song)
{
return Songs.FirstOrDefault(sng => sng.SongID == song.SongID) != null ? true : false;
}
}
}
+48
View File
@@ -0,0 +1,48 @@
using System;
using System.Collections.Generic;
using System.Linq;
// using MySql.Data.EntityFrameworkCore;
// using MySql.Data;
// using MySql.Data.EntityFrameworkCore.Extensions;
// using MySql.Data.Entity;
// using MySql.Data.MySqlClient;
using Microsoft.EntityFrameworkCore;
using Icarus.Models;
namespace Icarus.Database.Contexts
{
public class UserContext : DbContext
{
public DbSet<User> Users { get; set; }
public UserContext(DbContextOptions<UserContext> options) : base(options) { }
public UserContext(string connString) : base(new DbContextOptionsBuilder<UserContext>()
.UseMySQL(connString).Options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>()
.ToTable("User");
modelBuilder.Entity<User>()
.Property(u => u.LastLogin).IsRequired(false);
modelBuilder.Entity<User>()
.Property(u => u.DateCreated).HasDefaultValue(DateTime.Now);
}
public User RetrieveRecord(User user)
{
return Users.FirstOrDefault(usr => usr.UserID == user.UserID);
}
public bool DoesRecordExist(User user)
{
return Users.FirstOrDefault(usr => usr.UserID == user.UserID) != null ? true : false;
}
}
}
+34
View File
@@ -0,0 +1,34 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net6</TargetFramework>
<AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
<Version>0.1.9</Version>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BCrypt.Net-Next" Version="4.0.2" />
<PackageReference Include="DotNetZip" Version="1.15.0" />
<PackageReference Include="EntityFramework" Version="6.4.4" />
<PackageReference Include="ID3" Version="0.6.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="6.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="5.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="5.0.8">
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="MySql.EntityFrameworkCore" Version="5.0.8" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="NLog.Web.AspNetCore" Version="4.14.0" />
<PackageReference Include="RestSharp" Version="106.15.0" />
<PackageReference Include="SevenZip" Version="19.0.0" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.15.0" />
<PackageReference Include="taglib" Version="2.1.0" />
</ItemGroup>
<ItemGroup>
<Content Update="nlog.config" CopyToOutputDirectory="PreserveNewest" />
<Content Include="Images/Stock/*.*" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
</Project>
+1 -1
View File
@@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2019 Kun Deng Copyright (c) 2021 Kun Deng
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
View File
+28
View File
@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using Newtonsoft.Json;
namespace Icarus.Models
{
public class Album
{
[JsonProperty("album_id")]
public int AlbumID { get; set; }
[JsonProperty("title")]
public string Title { get; set; }
[JsonProperty("album_artist")]
[Column("Artist")]
public string AlbumArtist { get; set; }
[JsonProperty("song_count")]
[NotMapped]
public int SongCount { get; set; }
[JsonProperty("year")]
public int Year { get; set; }
[JsonIgnore]
[NotMapped]
public List<Song> Songs { get; set; }
}
}
+24
View File
@@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using Newtonsoft.Json;
namespace Icarus.Models
{
public class Artist
{
[JsonProperty("artist_id")]
public int ArtistID { get; set; }
[JsonProperty("name")]
[Column("Artist")]
public string Name { get; set; }
[JsonProperty("song_count")]
[NotMapped]
public int SongCount { get; set; }
[JsonIgnore]
[NotMapped]
public List<Song> Songs { get; set; }
}
}
+12
View File
@@ -0,0 +1,12 @@
using System;
using Newtonsoft.Json;
namespace Icarus.Models
{
public class BaseResult
{
[JsonProperty("message")]
public string Message { get; set; }
}
}
+43
View File
@@ -0,0 +1,43 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;
using Newtonsoft.Json;
namespace Icarus.Models
{
public class CoverArt
{
#region Properties
[JsonProperty("cover_art_id")]
public int CoverArtID { get; set; }
[JsonProperty("title")]
public string SongTitle { get; set; }
[JsonIgnore]
public string ImagePath { get; set; }
[JsonIgnore]
[NotMapped]
public int SongID { get; set; }
[JsonIgnore]
[NotMapped]
public List<Song> Songs { get; set; }
#endregion
#region Methods
public string GenerateFilename(int flag)
{
const int length = 25;
const string chars = "ABCDEF0123456789";
var random = new Random();
var filename = new string(Enumerable.Repeat(chars, length).Select(s =>
s[random.Next(s.Length)]).ToArray());
var extension = ".mp3";
return (flag == 0) ? filename : $"{filename}{extension}";
}
#endregion
}
}
+23
View File
@@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using Newtonsoft.Json;
namespace Icarus.Models
{
public class Genre
{
[JsonProperty("genre_id")]
public int GenreID { get; set; }
[JsonProperty("genre")]
[Column("Category")]
public string GenreName { get; set; }
[JsonProperty("song_count")]
[NotMapped]
public int SongCount { get; set; }
[JsonIgnore]
[NotMapped]
public List<Song> Songs { get; set; }
}
}
+20
View File
@@ -0,0 +1,20 @@
using System;
using Newtonsoft.Json;
namespace Icarus.Models
{
public class LoginResult : BaseResult
{
[JsonProperty("user_id")]
public int UserID { get; set; }
[JsonProperty("username")]
public string Username { get; set; }
[JsonProperty("token")]
public string Token { get; set; }
[JsonProperty("token_type")]
public string TokenType { get; set; }
[JsonProperty("expiration")]
public int Expiration { get; set; }
}
}
+14
View File
@@ -0,0 +1,14 @@
using System;
using Newtonsoft.Json;
namespace Icarus.Models
{
public class RegisterResult : BaseResult
{
[JsonProperty("username")]
public string Username { get; set; }
[JsonProperty("successfully_registered")]
public bool SuccessfullyRegistered { get; set; }
}
}
+86
View File
@@ -0,0 +1,86 @@
using System;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using Newtonsoft.Json;
namespace Icarus.Models
{
public class Song
{
#region Properties
[JsonProperty("song_id")]
public int SongID { get; set; }
[JsonProperty("title")]
public string Title { get; set; }
[JsonProperty("album")]
[Column("Album")]
public string AlbumTitle { get; set; }
[JsonProperty("artist")]
public string Artist { get; set; }
[JsonProperty("album_artist")]
public string AlbumArtist { get; set; }
[JsonProperty("year")]
public int? Year { get; set; }
[JsonProperty("genre")]
public string Genre { get; set; }
[JsonProperty("duration")]
public int Duration { get; set; }
[JsonProperty("filename")]
public string Filename { get; set; }
[JsonIgnore]
public string SongDirectory { get; set; }
[JsonProperty("track")]
public int Track { get; set; } = 0;
[JsonProperty("track_count")]
public int TrackCount { get; set; } = 0;
[JsonProperty("disc")]
public int Disc { get; set; } = 0;
[JsonProperty("disc_count")]
public int DiscCount { get; set; } = 0;
[JsonIgnore]
public int? AlbumID { get; set; }
[JsonIgnore]
public int? ArtistID { get; set; }
[JsonIgnore]
public int? GenreID { get; set; }
[JsonIgnore]
public int? CoverArtID { get; set; }
[JsonProperty("date_created")]
public DateTime DateCreated { get; set; }
#endregion
#region Constructors
#endregion
#region Methods
public string SongPath()
{
var fullPath = SongDirectory;
if (fullPath[fullPath.Length -1] != '/')
{
fullPath += "/";
}
fullPath += Filename;
return fullPath;
}
public string GenerateFilename(int flag = 0)
{
const int length = 25;
const string chars = "ABCDEF0123456789";
var random = new Random();
var filename = new string(Enumerable.Repeat(chars, length).Select(s =>
s[random.Next(s.Length)]).ToArray());
var extension = ".mp3";
return flag == 0 ? filename : $"{filename}{extension}";
}
#endregion
}
}
+12
View File
@@ -0,0 +1,12 @@
using System;
using System.Text;
namespace Icarus.Models
{
public class SongData
{
public int ID { get; set; }
public byte[] Data { get; set; }
public int SongID { get; set; }
}
}
+14
View File
@@ -0,0 +1,14 @@
using System;
using Newtonsoft.Json;
namespace Icarus.Models
{
public class SongResult
{
[JsonProperty("message")]
public string Message { get; set; }
[JsonProperty("song_title")]
public string SongTitle { get; set; }
}
}
+39
View File
@@ -0,0 +1,39 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Newtonsoft.Json;
namespace Icarus.Models
{
[Table("User")]
public class User
{
[JsonProperty("user_id")]
[Column("UserID")]
[Key]
public int UserID { get; set; }
[JsonProperty("username")]
public string Username { get; set; }
[JsonProperty("password")]
public string Password { get; set; }
[JsonProperty("email")]
public string Email { get; set; }
[JsonProperty("phone")]
[Column("Phone")]
public string Phone { get; set; }
[JsonProperty("firstname")]
public string Firstname { get; set; }
[JsonProperty("lastname")]
public string Lastname { get; set; }
[JsonProperty("email_verified")]
[NotMapped]
public bool EmailVerified { get; set; }
[JsonProperty("date_created")]
public DateTime DateCreated { get; set; }
[JsonProperty("status")]
public string Status { get; set; }
[JsonProperty("last_login")]
public DateTime? LastLogin { get; set; }
}
}
+46
View File
@@ -0,0 +1,46 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using NLog.Web;
namespace Icarus
{
public class Program
{
public static void Main(string[] args)
{
var logger = NLog.Web.NLogBuilder.ConfigureNLog("nlog.config").GetCurrentClassLogger();
try
{
logger.Debug("init main");
CreateHostBuilder(args).Build().Run();
}
catch (Exception ex)
{
logger.Error(ex, "An error occurred");
throw;
}
finally
{
NLog.LogManager.Shutdown();
}
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
}
+30
View File
@@ -0,0 +1,30 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:20781",
"sslPort": 44394
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "api/values",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"Icarus": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "api/values",
"applicationUrl": "http://localhost:5002",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
+44 -64
View File
@@ -1,6 +1,6 @@
# Icarus # Icarus
Icarus is a music streaming API Server, allowing access to stream your personal music collection Icarus is a music streaming API Server that interacts with [Mear](https://github.com/amazing-username/mear).
### Interfacing With Icarus ### Interfacing With Icarus
@@ -8,21 +8,16 @@ One can interface with Icarus the music server either by:
* [Mear](https://github.com/amazing-username/mear) - Partially implemented (under development) * [Mear](https://github.com/amazing-username/mear) - Partially implemented (under development)
* [IcarusDownloadManager](https://github.com/amazing-username/IcarusDownloadManager) * [IcarusDownloadManager](https://github.com/amazing-username/IcarusDownloadManager)
* Create your own client to interact with the API
## Built With ## Built With
* C++ >= C++17 * C# [.NET](https://dotnet.microsoft.com/) 6
* GCC >= 8.0 * [MySql](https://www.nuget.org/packages/MySql.Data/)
* [json](https://www.github.com/nlohmann/json) * [Newtonsoft.Json](https://www.newtonsoft.com/json)
* [cpr](https://www.github.com/whoshuu/cpr) * [TagLib#](https://github.com/mono/taglib-sharp)
* [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) ![image](https://user-images.githubusercontent.com/14333136/56252069-28532d00-6084-11e9-896d-1a3c378014ef.png)
@@ -47,10 +42,10 @@ Create the API and enter an approrpiate name and identified. For the identified,
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. 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 ```Json
"domain": "[domain].auth0.com", "Auth0": {
"api_identifier": "https://[identifier]/api", "Domain": "[domain].auth0.com",
"client_id": "iamunique", "ApiIdentifier": "https://[identifier]/api"
"client_secret": "Icankeepasecret" },
``` ```
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. 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.
@@ -77,90 +72,75 @@ From the Application page, copy the client id and client secret. These values wi
<h1 align="center"> <h1 align="center">
<img src="Images/Configuration/api_cred.png" width=100%> <img src="Images/Configuration/api_cred.png" width=100%>
</h1> </h1>
Enter the information in the corresponding ``authcredentials.json`` file Enter the information in the corresponding appsettings json file
```Json ```Json
{ "Auth0": {
"domain": "somedomain.auth0.com", "ClientId":"",
"api_identifier": "https://squawk/api" "ClientSecret":""
"client_id": "clientidhere", },
"client_secret": "illkeepyoumydirtylittlesecret"
}
``` ```
### API filesystem paths ### 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: For the purposes of properly uploading, downloading, updating, deleting, and streaming songs the API filesystem paths must be configured. What is meant by this is that the `RootMusicPath` directory where all music will be stored must exist as well as the `ArchivePath` and `TemporaryMusicPath` paths. An example on a Linux system:
```Json ```Json
{ {
"root_music_path": "/dev/null/music/", "RootMusicPath": "/home/dev/null/music/",
"temp_root_path": "/dev/null/music/temp/", "TemporaryMusicPath": "/home/dev/null/music/temp/",
"cover_root_path": "/dev/null/music/coverArt/", "ArchivePath": "/home/dev/null/music/archive/"
"archive_root_path": "/dev/null/music/archive/"
} }
``` ```
* `root_music_path` - Where music will be stored in the following convention: *`Artist/Album/Songs`* * RootMusicPath - 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` * TemporaryMusicPath - 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 `RootMusicPath`
* `cover_root_path` - Where cover art of music will be saved to. * ArchivePath - 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
* `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. **Note**: The `TemporaryMusic` or `ArchivePath` does not have to be located in the `RootMusicPath`. Ensure that the permissions are properly set for all of the paths.
### Database connection string ### 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: In order for Database functionality to be operable, there must be a valid connection string and credentials with appropriate permissions. **At the moment there is only support for MySQL**. Depending on your environment `Release` or `Debug` you will need to edit the appsettings.json or appsettings.Development.json accordingly. An example of the fields to change are below:
```Json ```Json
{ {
"server": "localhost", "ConnectionStrings": {
"database": "my_db", "DefaultConnection": "Server=localhost;Database=my_db;Uid=admin;Pwd=toughpassword;"
"username": "admin", }
"password": "toughpassword"
} }
``` ```
* server - The address or domain name of the MySQL server * Server - The address or domain name of the MySQL server
* database - The database name * Database - The database name
* username - Username * Uid - Username
* password - Self-explanatory * 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. 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 ### Migrations
Prior to starting the API, the database must be created. The following tables are required: Prior to starting the API, the Migrations must be applied. There are 6 tables with migrations being applied and thy are:
* User * Users
* Song * Song
* Album * Album
* Artist * Artist
* Year * Year
* Genre * 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: There is a script for Linux systems to apply these migrations, it can be found in the [Scripts/Migrations/Linux](https://github.com/amazing-username/Icarus/blob/master/Scripts/Migrations/Linux/AddUpdate.sh) directory. Just merely execute:
```shell ```shell
mysql -u dblikedecibel -p < Scripts/MySQL/create_database.sql scripts/Migrations/Linux/AddUpdate.sh
``` ```
Or you can manually add the migrations like so for each migration:
From this point the database has been successfully created. Metadata and song filesystem locations can be saved. ```shell
dotnet ef migrations Add [Migration] --context [Migration]Context
## Building and Running
``` ```
git clone --recursive https://github.com/kdeng00/icarus Then update the migrations to the database like so<sup>*</sup>:
cd 3rdparty/libbcrypt/ ```shell
make dotnet ef database update --context [Migration]Context
cp bcrypt.a libbcrypt.a
cp bcrypt.o libbcrypt.o
cd ../..
mkdir build
cd build
conan install ..
cmake ..
make
bin/icarus
``` ```
Runs the server on localhost port 5002 From this point the database has been successfully configured. Metadata and song filesystem locations can be saved.
<sup>*</sup> Will only need to execute this for UserContext and SongContext because the Song table has relational constraints with Album, Artist, Year, and Genre.
## Contributing ## Contributing
+46 -33
View File
@@ -3,84 +3,97 @@ CREATE DATABASE Icarus;
USE Icarus; USE Icarus;
CREATE TABLE CoverArt ( CREATE TABLE CoverArt (
CoverArtId INT NOT NULL AUTO_INCREMENT, CoverArtID INT NOT NULL AUTO_INCREMENT,
SongTitle TEXT NOT NULL, SongTitle TEXT NOT NULL,
ImagePath TEXT NOT NULL, ImagePath TEXT NOT NULL,
PRIMARY KEY (CoverArtId) PRIMARY KEY (CoverArtID)
); );
CREATE TABLE Album ( CREATE TABLE Album (
AlbumId INT NOT NULL AUTO_INCREMENT, AlbumID INT NOT NULL AUTO_INCREMENT,
Title TEXT NOT NULL, Title TEXT NOT NULL,
Artist TEXT NOT NULL,
Year INT NOT NULL, Year INT NOT NULL,
PRIMARY KEY (AlbumId) PRIMARY KEY (AlbumID)
); );
CREATE TABLE Artist ( CREATE TABLE Artist (
ArtistId INT NOT NULL AUTO_INCREMENT, ArtistID INT NOT NULL AUTO_INCREMENT,
Artist TEXT NOT NULL, Artist TEXT NOT NULL,
PRIMARY KEY (ArtistId) PRIMARY KEY (ArtistID)
); );
CREATE TABLE Genre ( CREATE TABLE Genre (
GenreId INT NOT NULL AUTO_INCREMENT, GenreID INT NOT NULL AUTO_INCREMENT,
Category TEXT NOT NULL, Category TEXT NOT NULL,
PRIMARY KEY (GenreId) PRIMARY KEY (GenreID)
); );
CREATE TABLE Year (
YearId INT NOT NULL AUTO_INCREMENT,
Year INT NOT NULL,
PRIMARY KEY (YearId)
);
CREATE TABLE Song ( CREATE TABLE Song (
SongId INT NOT NULL AUTO_INCREMENT, SongID INT NOT NULL AUTO_INCREMENT,
Title TEXT NOT NULL, Title TEXT NOT NULL,
Artist TEXT NOT NULL, Artist TEXT NOT NULL,
Album TEXT NOT NULL, Album TEXT NOT NULL,
AlbumArtist TEXT NOT NULL,
Genre TEXT NOT NULL, Genre TEXT NOT NULL,
Year INT NOT NULL, Year INT NOT NULL,
Duration INT NOT NULL, Duration INT NOT NULL,
Track INT NOT NULL, Track INT NOT NULL,
TrackCount INT NOT NULL,
Disc INT NOT NULL, Disc INT NOT NULL,
SongPath TEXT NOT NULL, DiscCount INT NOT NULL,
CoverArtId INT NOT NULL, SongDirectory TEXT NOT NULL,
ArtistId INT NOT NULL, Filename TEXT NOT NULL,
AlbumId INT NOT NULL, CoverArtID INT NOT NULL,
GenreId INT NOT NULL, ArtistID INT NOT NULL,
YearId INT NOT NULL, AlbumID INT NOT NULL,
GenreID INT NOT NULL,
DateCreated DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (SongId), PRIMARY KEY (SongID),
CONSTRAINT FK_CoverArtId FOREIGN KEY (CoverArtId) REFERENCES CoverArt (CoverArtId), CONSTRAINT FK_Song_CoverArtID FOREIGN KEY (CoverArtID) REFERENCES CoverArt (CoverArtID),
CONSTRAINT FK_ArtistId FOREIGN KEY (ArtistId) REFERENCES Artist (ArtistId), CONSTRAINT FK_Song_ArtistID FOREIGN KEY (ArtistID) REFERENCES Artist (ArtistID),
CONSTRAINT FK_AlbumId FOREIGN KEY (AlbumId) REFERENCES Album (AlbumId), CONSTRAINT FK_Song_AlbumID FOREIGN KEY (AlbumID) REFERENCES Album (AlbumID),
CONSTRAINT FK_GenreId FOREIGN KEY (GenreId) REFERENCES Genre (GenreId), CONSTRAINT FK_Song_GenreID FOREIGN KEY (GenreID) REFERENCES Genre (GenreID)
CONSTRAINT FK_YearId FOREIGN KEY (YearId) REFERENCES Year (YearId)
); );
CREATE TABLE User ( CREATE TABLE User (
UserId INT NOT NULL AUTO_INCREMENT, UserID INT NOT NULL AUTO_INCREMENT,
Firstname TEXT NOT NULL, Firstname TEXT NOT NULL,
Lastname TEXT NOT NULL, Lastname TEXT NOT NULL,
Email TEXT NOT NULL, Email TEXT NOT NULL,
Phone TEXT NOT NULL, Phone TEXT NOT NULL,
Username TEXT NOT NULL, Username TEXT NOT NULL,
Password TEXT NOT NULL, Password TEXT NOT NULL,
DateCreated DATETIME DEFAULT CURRENT_TIMESTAMP,
Status TEXT DEFAULT 'Active',
LastLogin DATETIME NULL,
PRIMARY KEY (UserId) PRIMARY KEY (UserID)
); );
CREATE TABLE Salt ( CREATE TABLE Salt (
SaltId INT NOT NULL AUTO_INCREMENT, SaltID INT NOT NULL AUTO_INCREMENT,
Salt TEXT NOT NULL, Salt TEXT NOT NULL,
UserId INT NOT NULL, UserID INT NOT NULL,
PRIMARY KEY (SaltId), PRIMARY KEY (SaltID),
CONSTRAINT FK_UserId FOREIGN KEY (UserId) REFERENCES User (UserId) CONSTRAINT FK_Salt_UserID FOREIGN KEY (UserID) REFERENCES User (UserID)
);
CREATE TABLE Token (
TokenID INT NOT NULL AUTO_INCREMENT,
AccessToken TEXT NOT NULL,
TokenType TEXT NOT NULL,
Expires DATETIME DEFAULT CURRENT_TIMESTAMP,
Issued DATETIME DEFAULT CURRENT_TIMESTAMP,
UserID INT NOT NULL,
PRIMARY KEY (TokenID),
CONSTRAINT FK_Token_UserID FOREIGN KEY (UserID) REFERENCES User (UserID)
); );
-1
View File
@@ -2,5 +2,4 @@ delete from Song;
delete from Album; delete from Album;
delete from Artist; delete from Artist;
delete from Genre; delete from Genre;
delete from Year;
delete from CoverArt; delete from CoverArt;
+1
View File
@@ -0,0 +1 @@
drop database Icarus;
+135
View File
@@ -0,0 +1,135 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;
using NLog;
using NLog.Web;
using NLog.Web.AspNetCore;
using Icarus.Authorization;
using Icarus.Authorization.Handlers;
using Icarus.Database.Contexts;
namespace Icarus
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
var auth_id = Configuration["Auth0:Domain"];
var domain = $"https://{auth_id}/";
var audience = Configuration["Auth0:ApiIdentifier"];
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = domain;
options.Audience = audience;
});
services.AddAuthorization(options =>
{
options.AddPolicy("download:songs", policy =>
policy.Requirements
.Add(new HasScopeRequirement("download:songs", domain)));
options.AddPolicy("download:cover_art", policy =>
policy.Requirements
.Add(new HasScopeRequirement("download:cover_art", domain)));
options.AddPolicy("upload:songs", policy =>
policy.Requirements
.Add(new HasScopeRequirement("upload:songs", domain)));
options.AddPolicy("delete:songs", policy =>
policy.Requirements
.Add(new HasScopeRequirement("delete:songs", domain)));
options.AddPolicy("read:song_details", policy =>
policy.Requirements
.Add(new HasScopeRequirement("read:song_details", domain)));
options.AddPolicy("update:songs", policy =>
policy.Requirements
.Add(new HasScopeRequirement("update:songs", domain)));
options.AddPolicy("read:artists", policy =>
policy.Requirements
.Add(new HasScopeRequirement("read:artists", domain)));
options.AddPolicy("read:albums", policy =>
policy.Requirements
.Add(new HasScopeRequirement("read:albums", domain)));
options.AddPolicy("read:genre", policy =>
policy.Requirements
.Add(new HasScopeRequirement("read:genre", domain)));
options.AddPolicy("read:year", policy =>
policy.Requirements
.Add(new HasScopeRequirement("read:year", domain)));
options.AddPolicy("stream:songs", policy =>
policy.Requirements
.Add(new HasScopeRequirement("stream:songs", domain)));
});
services.AddSingleton<IAuthorizationHandler, HasScopeHandler>();
var connString = Configuration.GetConnectionString("DefaultConnection");
services.AddDbContext<SongContext>(options => options.UseMySQL(connString));
services.AddDbContext<AlbumContext>(options => options.UseMySQL(connString));
services.AddDbContext<ArtistContext>(options => options.UseMySQL(connString));
services.AddDbContext<UserContext>(options => options.UseMySQL(connString));
services.AddDbContext<GenreContext>(options => options.UseMySQL(connString));
services.AddDbContext<CoverArtContext>(options => options.UseMySQL(connString));
services.AddControllers()
.AddNewtonsoftJson();
}
// Called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// NOTE: Dev-related configuration can be done when env.IsDevelopment() evaluated to true
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}
+10
View File
@@ -0,0 +1,10 @@
using System;
namespace Icarus.Types
{
public enum CoverArtField
{
SongTitle = 0,
ImagePath
};
}
+10
View File
@@ -0,0 +1,10 @@
using System;
namespace Icarus.Types
{
public enum DirectoryType
{
Music = 0,
CoverArt
};
}
+22
View File
@@ -0,0 +1,22 @@
{
"Logging": {
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
}
},
"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"
}
-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
-235
View File
@@ -1,235 +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 "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);
return createResponse(Status::CODE_200, "OK");
}
// 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::SongDto::createShared();
song->id = songDb.id;
song->title = songDb.title.c_str();
song->artist = songDb.artist.c_str();
song->album = songDb.album.c_str();
song->genre = songDb.genre.c_str();
song->year = songDb.year;
song->duration = songDb.duration;
song->track = songDb.track;
song->disc = songDb.disc;
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);
OATPP_ASSERT_HTTP(songRepo.doesSongExist(songDb, type::SongFilter::id) , Status::CODE_403,
"song does not exist");
std::cout << "song exists" << std::endl;
songDb = songRepo.retrieveRecord(songDb, type::SongFilter::id);
auto song = dto::SongDto::createShared();
song->id = songDb.id;
song->title = songDb.title.c_str();
song->artist = songDb.artist.c_str();
song->album = songDb.album.c_str();
song->genre = songDb.genre.c_str();
song->year = songDb.year;
song->duration = songDb.duration;
song->track = songDb.track;
song->disc = songDb.disc;
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);
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 = manager::SongManager::songDtoConv(songDto);
manager::SongManager songMgr(m_bConf);
songMgr.updateSong(updatedSong);
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);
sngMgr.deleteSong(song);
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);
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::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
-33
View File
@@ -1,33 +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);
};
#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
-22
View File
@@ -1,22 +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 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
-54
View File
@@ -1,54 +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&);
void saveSong(model::Song&);
void deleteSong(model::Song&);
void updateSong(model::Song&);
static model::Song songDtoConv(dto::SongDto::ObjectWrapper&);
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

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