* .NET 8 migration

* Fixed bugs with song management

* More cleanup and making some constants

* Updated yml

* Clean up

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

* Removing comments

* Fixed song deletion issue

* Added functionality to delete song directories

* Updated README
This commit was merged in pull request #89.
This commit is contained in:
Kun Deng
2024-06-15 12:17:12 -04:00
committed by GitHub
parent ececcb5ba3
commit 23c50de468
22 changed files with 182 additions and 454 deletions
+1 -1
View File
@@ -16,7 +16,7 @@ jobs:
- name: Setup .NET - name: Setup .NET
uses: actions/setup-dotnet@v2 uses: actions/setup-dotnet@v2
with: with:
dotnet-version: 6.0.x dotnet-version: 8.0.x
- name: Restore dependencies - name: Restore dependencies
run: dotnet restore run: dotnet restore
- name: Build - name: Build
+6 -7
View File
@@ -1,5 +1,4 @@
using System; using System;
using System.Security.Cryptography;
using Microsoft.IdentityModel.Tokens; using Microsoft.IdentityModel.Tokens;
using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Crypto.Parameters;
@@ -10,11 +9,11 @@ namespace Icarus.Certs;
public class SigningIssuerCertificate : IDisposable public class SigningIssuerCertificate : IDisposable
{ {
private readonly RSACryptoServiceProvider _rsa; private readonly System.Security.Cryptography.RSACryptoServiceProvider _rsa;
public SigningIssuerCertificate() public SigningIssuerCertificate()
{ {
_rsa = new RSACryptoServiceProvider(); _rsa = new System.Security.Cryptography.RSACryptoServiceProvider();
} }
public RsaSecurityKey GetIssuerSigningKey(string publicKeyPath) public RsaSecurityKey GetIssuerSigningKey(string publicKeyPath)
@@ -26,7 +25,7 @@ public class SigningIssuerCertificate : IDisposable
{ {
var pem = new PemReader(reader); var pem = new PemReader(reader);
var o = (RsaKeyParameters)pem.ReadObject(); var o = (RsaKeyParameters)pem.ReadObject();
var parameters = new RSAParameters(); var parameters = new System.Security.Cryptography.RSAParameters();
parameters.Modulus = o.Modulus.ToByteArray(); parameters.Modulus = o.Modulus.ToByteArray();
parameters.Exponent = o.Exponent.ToByteArray(); parameters.Exponent = o.Exponent.ToByteArray();
_rsa.ImportParameters(parameters); _rsa.ImportParameters(parameters);
@@ -45,11 +44,11 @@ public class SigningIssuerCertificate : IDisposable
public class SigningAudienceCertificate : IDisposable public class SigningAudienceCertificate : IDisposable
{ {
private readonly RSACryptoServiceProvider _rsa; private readonly System.Security.Cryptography.RSACryptoServiceProvider _rsa;
public SigningAudienceCertificate() public SigningAudienceCertificate()
{ {
_rsa = new RSACryptoServiceProvider(); _rsa = new System.Security.Cryptography.RSACryptoServiceProvider();
} }
public SigningCredentials GetAudienceSigningKey(string keyPath) public SigningCredentials GetAudienceSigningKey(string keyPath)
@@ -61,7 +60,7 @@ public class SigningAudienceCertificate : IDisposable
{ {
var pem = new PemReader(reader); var pem = new PemReader(reader);
var o = (RsaKeyParameters)pem.ReadObject(); var o = (RsaKeyParameters)pem.ReadObject();
var parameters = new RSAParameters(); var parameters = new System.Security.Cryptography.RSAParameters();
parameters.Modulus = o.Modulus.ToByteArray(); parameters.Modulus = o.Modulus.ToByteArray();
parameters.Exponent = o.Exponent.ToByteArray(); parameters.Exponent = o.Exponent.ToByteArray();
_rsa.ImportParameters(parameters); _rsa.ImportParameters(parameters);
+2 -1
View File
@@ -1,4 +1,3 @@
using System;
using System.IO; using System.IO;
namespace Icarus.Constants; namespace Icarus.Constants;
@@ -7,4 +6,6 @@ public class DirectoryPaths
{ {
public static string CoverArtPath => public static string CoverArtPath =>
Directory.GetCurrentDirectory() + "/Images/Stock/CoverArt.png"; Directory.GetCurrentDirectory() + "/Images/Stock/CoverArt.png";
public static string FILENAME_CHARACTERS = "ABCDEF0123456789";
public static int FILENAME_LENGTH = 25;
} }
+13
View File
@@ -0,0 +1,13 @@
namespace Icarus.Constants;
public class FileExtensions
{
// Contains file extension with period at the beginning
public static string MP3_EXTENSION = ".mp3";
// Contains file extension with period at the beginning
public static string WAV_EXTENSION = ".wav";
// Contains file extension with period at the beginning
public static string JPG_EXTENSION = ".jpg";
}
+1
View File
@@ -47,6 +47,7 @@ public class CoverArtManager : BaseManager
{ {
_logger.Info("Attempting to delete cover art from the database"); _logger.Info("Attempting to delete cover art from the database");
_coverArtContext.Attach(coverArt);
_coverArtContext.Remove(coverArt); _coverArtContext.Remove(coverArt);
_coverArtContext.SaveChanges(); _coverArtContext.SaveChanges();
} }
+9 -13
View File
@@ -145,7 +145,7 @@ public class SongManager : BaseManager
{ {
try try
{ {
if (DeleteSongFromFilesystem(song)) if (DeleteSongFromFilesystem(song, true))
{ {
_logger.Error("Failed to delete the song"); _logger.Error("Failed to delete the song");
@@ -158,8 +158,8 @@ public class SongManager : BaseManager
var coverArt = coverMgr.GetCoverArt(song); var coverArt = coverMgr.GetCoverArt(song);
coverMgr.DeleteCoverArt(coverArt); coverMgr.DeleteCoverArt(coverArt);
coverMgr.DeleteCoverArtFromDatabase(coverArt);
DeleteSongFromDatabase(song); DeleteSongFromDatabase(song);
coverMgr.DeleteCoverArtFromDatabase(coverArt);
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -241,13 +241,7 @@ public class SongManager : BaseManager
} }
var coverMgr = new CoverArtManager(_config); var coverMgr = new CoverArtManager(_config);
var meta = new MetadataRetriever();
var coverArt = coverMgr.SaveCoverArt(coverArtData, song); var coverArt = coverMgr.SaveCoverArt(coverArtData, song);
meta.UpdateCoverArt(song, coverArt);
song.Duration = meta.RetrieveSongDuration(song.SongPath());
meta.UpdateMetadata(song, song);
DirectoryManager dirMgr = new DirectoryManager(_config, song); DirectoryManager dirMgr = new DirectoryManager(_config, song);
dirMgr.CreateDirectory(); dirMgr.CreateDirectory();
@@ -406,7 +400,7 @@ public class SongManager : BaseManager
} }
private bool DeleteSongFromFilesystem(Song song) private bool DeleteSongFromFilesystem(Song song, bool deleteDirectory = false)
{ {
var songPath = song.SongPath(); var songPath = song.SongPath();
@@ -415,6 +409,8 @@ public class SongManager : BaseManager
try try
{ {
System.IO.File.Delete(songPath); System.IO.File.Delete(songPath);
DeleteEmptyDirectories(ref song, ref song);
} }
catch(Exception ex) catch(Exception ex)
{ {
@@ -425,6 +421,7 @@ public class SongManager : BaseManager
return DoesSongExistOnFilesystem(song); return DoesSongExistOnFilesystem(song);
} }
private bool DoesSongExistOnFilesystem(Song song) private bool DoesSongExistOnFilesystem(Song song)
{ {
if (!System.IO.File.Exists(song.SongPath())) if (!System.IO.File.Exists(song.SongPath()))
@@ -494,13 +491,12 @@ public class SongManager : BaseManager
var albumMgr = new AlbumManager(_config); var albumMgr = new AlbumManager(_config);
var artistMgr = new ArtistManager(_config); var artistMgr = new ArtistManager(_config);
var genreMgr = new GenreManager(_config); var genreMgr = new GenreManager(_config);
albumMgr.DeleteAlbumFromDatabase(song);
artistMgr.DeleteArtistFromDatabase(song);
genreMgr.DeleteGenreFromDatabase(song);
var sngContext = new SongContext(_connectionString); var sngContext = new SongContext(_connectionString);
sngContext.Songs.Remove(song); sngContext.Songs.Remove(song);
sngContext.SaveChanges(); sngContext.SaveChanges();
artistMgr.DeleteArtistFromDatabase(song);
albumMgr.DeleteAlbumFromDatabase(song);
genreMgr.DeleteGenreFromDatabase(song);
} }
#endregion #endregion
} }
+18 -173
View File
@@ -2,20 +2,13 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Security.Claims; using System.Security.Claims;
using System.Security.Cryptography;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.IdentityModel.Tokens.Jwt; using System.IdentityModel.Tokens.Jwt;
using System.Text; using System.Text;
using JWT;
using JWT.Serializers;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens; using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json; using Newtonsoft.Json;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Security;
using RestSharp; using RestSharp;
using Icarus.Models; using Icarus.Models;
@@ -87,35 +80,6 @@ public class TokenManager : BaseManager
} }
[Obsolete("Asymmetric key signing for tokens have been deprecated")]
public LoginResult LogIn(User user)
{
var tokenResult = new TokenTierOne();
tokenResult.TokenType = "Jwt";
var privateKey = ReadKeyContent(_privateKeyPath).Result;
var publicKey = ReadKeyContent(_publicKeyPath).Result;
var payload = Payload();
var token = CreateToken(payload, privateKey);
tokenResult.AccessToken = token;
var expClaim = payload.FirstOrDefault(cl =>
{
return cl.Type.Equals("exp");
});
tokenResult.Expiration = System.Convert.ToInt32(expClaim.Value);
return new LoginResult
{
UserID = user.UserID, Username = user.Username, Token = tokenResult.AccessToken,
TokenType = tokenResult.TokenType, Expiration = tokenResult.Expiration,
Message = "Successfully retrieved token"
};
}
public LoginResult LoginSymmetric(User user) public LoginResult LoginSymmetric(User user)
{ {
var tokenResult = new TokenTierOne(); var tokenResult = new TokenTierOne();
@@ -124,16 +88,25 @@ public class TokenManager : BaseManager
var payload = Payload(); var payload = Payload();
payload.Add(new System.Security.Claims.Claim("user_id", user.UserID.ToString(), ClaimValueTypes.Integer)); payload.Add(new System.Security.Claims.Claim("user_id", user.UserID.ToString(), ClaimValueTypes.Integer));
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["JWT:Secret"])); var tokenHandler = new JwtSecurityTokenHandler();
var signIn = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var key = Encoding.ASCII.GetBytes(_config["JWT:Secret"]);
var token = new JwtSecurityToken( var tokenDescriptor = new SecurityTokenDescriptor
_config["JWT:Issuer"], {
_config["JWT:Audience"], Subject = new ClaimsIdentity(new Claim[]
payload, {
expires: DateTime.UtcNow.AddMinutes(30), new Claim("user_id", user.UserID.ToString(), ClaimValueTypes.Integer)
signingCredentials: signIn); // Add more claims as needed
}),
Expires = DateTime.UtcNow.AddHours(1),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature),
Issuer = _config["Jwt:Issuer"], // Add this line
Audience = _config["Jwt:Audience"]
};
tokenResult.AccessToken = new JwtSecurityTokenHandler().WriteToken(token); var token = tokenHandler.CreateToken(tokenDescriptor);
tokenResult.AccessToken = tokenHandler.WriteToken(token);
var expClaim = payload.FirstOrDefault(cl => var expClaim = payload.FirstOrDefault(cl =>
{ {
@@ -152,59 +125,6 @@ public class TokenManager : BaseManager
}; };
} }
[Obsolete("Asymmetric key signing for tokens have been deprecated")]
public bool IsTokenValid(string scope, string accessToken)
{
var result = false;
var token = DecodeToken(accessToken);
if (token == null || token.Erroneous())
{
return result;
}
result = (!token.TokenExpired() && token.ContainsScope(scope)) ? true : false;
// What would make a token valid?
// 1. The expiration date must be before the current date
// 2. The desired scope must be part of the scopes within the access token
// 3. Must be able to be decoded
return result;
}
[Obsolete("Asymmetric key signing for tokens have been deprecated")]
public Token DecodeToken(string accessToken)
{
var rsaParams = GetRSAPublic(_publicKey);
Token tok = null;
try
{
using (var rsa = new RSACryptoServiceProvider())
{
rsa.ImportParameters(rsaParams);
IJsonSerializer serializer = new JsonNetSerializer();
IDateTimeProvider provider = new UtcDateTimeProvider();
IJwtValidator validator = new JwtValidator(serializer, provider);
IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();
var algorithm = new JWT.Algorithms.RS256Algorithm(rsa);
IJwtDecoder decoder = new JwtDecoder(serializer, validator, urlEncoder, algorithm);
var json = decoder.Decode(accessToken);
tok = JsonConvert.DeserializeObject<Token>(json);
}
}
catch (Exception ex)
{
_logger.Error("An error occurred: {0}", ex.Message);
}
return tok;
}
private string AllScopes() private string AllScopes()
{ {
@@ -266,81 +186,6 @@ public class TokenManager : BaseManager
return claim; return claim;
} }
[Obsolete("Asymmetric key signing for tokens have been deprecated")]
private string CreateToken(List<Claim> claims, string privateKey)
{
var token = string.Empty;
if (string.IsNullOrEmpty(privateKey))
{
privateKey = ReadKeyContent(_privateKeyPath).Result;
}
RSAParameters rsaParams;
using (var tr = new System.IO.StringReader(privateKey))
{
var pemReader = new PemReader(tr);
var keyPair = pemReader.ReadObject() as AsymmetricCipherKeyPair;
if (keyPair == null)
{
throw new Exception("Could not read RSA private key");
}
var privateRsaParams = keyPair.Private as RsaPrivateCrtKeyParameters;
rsaParams = DotNetUtilities.ToRSAParameters(privateRsaParams);
}
using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
{
var rsaParamsPublic = GetRSAPublic(ReadKeyContent(_publicKeyPath).Result);
var rsaPublic = new RSACryptoServiceProvider();
rsa.ImportParameters(rsaParams);
rsaPublic.ImportParameters(rsaParamsPublic);
Dictionary<string, object> payload = new Dictionary<string, object>();
foreach (var claim in claims)
{
var type = claim.Type;
var val = Int32.TryParse(claim.Value, out _);
if (val)
{
payload.Add(type, Convert.ToInt32(claim.Value));
}
else
{
payload.Add(type, claim.Value);
}
}
var algorithm = new JWT.Algorithms.RS256Algorithm(rsaPublic, rsa);
IJsonSerializer serializer = new JsonNetSerializer();
IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();
IJwtEncoder encoder = new JwtEncoder(algorithm, serializer, urlEncoder);
token = encoder.Encode(payload, privateKey);
}
return token;
}
[Obsolete("Asymmetric key signing for tokens have been deprecated")]
private RSAParameters GetRSAPublic(string publicKey)
{
using (var tr = new System.IO.StringReader(publicKey))
{
var pemReader = new PemReader(tr);
var publicKeyParams = pemReader.ReadObject() as RsaKeyParameters;
if (publicKeyParams == null)
{
throw new Exception("Could not read RSA public key");
}
return DotNetUtilities.ToRSAParameters(publicKeyParams);
}
}
private async Task<string> ReadKeyContent(string filepath) private async Task<string> ReadKeyContent(string filepath)
{ {
return await System.IO.File.ReadAllTextAsync(filepath); return await System.IO.File.ReadAllTextAsync(filepath);
+1 -1
View File
@@ -83,7 +83,7 @@ public class SongCompression
Console.WriteLine(exMsg); Console.WriteLine(exMsg);
} }
if (songDetails.Filename.Contains(".mp3")) if (songDetails.Filename.Contains(Constants.FileExtensions.WAV_EXTENSION))
{ {
_compressedSongFilename = StripMP3Extension(songDetails.Filename); _compressedSongFilename = StripMP3Extension(songDetails.Filename);
} }
-11
View File
@@ -4,8 +4,6 @@ using System.Linq;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Icarus.Controllers.Managers;
namespace Icarus.Controllers.V1; namespace Icarus.Controllers.V1;
@@ -39,14 +37,5 @@ public class BaseController : ControllerBase
return token; return token;
} }
[ApiExplorerSettings(IgnoreApi = true)]
[Obsolete("Asymmetric key signing for tokens have been deprecated")]
protected bool IsTokenValid(string scope)
{
var token = ParseBearerTokenFromHeader();
var tokMgr = new TokenManager(_config);
return tokMgr.IsTokenValid(scope, token);
}
#endregion #endregion
} }
+1 -1
View File
@@ -148,7 +148,7 @@ public class SongDataController : BaseController
_songMgr.DeleteSong(songMetaData); _songMgr.DeleteSong(songMetaData);
return Ok(); return Ok(songMetaData);
} }
} }
+6 -1
View File
@@ -53,7 +53,12 @@ public class SongStreamController : BaseController
var stream = new FileStream(song.SongPath(), FileMode.Open, FileAccess.Read); var stream = new FileStream(song.SongPath(), FileMode.Open, FileAccess.Read);
stream.Position = 0; stream.Position = 0;
var filename = $"{song.Title}.mp3"; var filename = song.Filename;
if (string.IsNullOrEmpty(song.Filename))
{
filename = song.GenerateFilename();
}
_logger.LogInformation("Starting to stream song...>"); _logger.LogInformation("Starting to stream song...>");
Console.WriteLine("Starting to streamsong..."); Console.WriteLine("Starting to streamsong...");
-32
View File
@@ -25,38 +25,6 @@ public class SongContext : DbContext
modelBuilder.Entity<Song>() modelBuilder.Entity<Song>()
.ToTable("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>() modelBuilder.Entity<Song>()
.Property(s => s.Year) .Property(s => s.Year)
.IsRequired(false); .IsRequired(false);
-5
View File
@@ -2,11 +2,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; 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 Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
+12 -10
View File
@@ -1,32 +1,34 @@
<Project Sdk="Microsoft.NET.Sdk.Web"> <Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel> <AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
<Version>0.1.10</Version> <Version>0.1.10</Version>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="BCrypt.Net-Next" Version="4.0.2" /> <PackageReference Include="BCrypt.Net-Next" Version="4.0.2" />
<PackageReference Include="BouncyCastle.Cryptography" Version="2.4.0" />
<PackageReference Include="DotNetZip" Version="1.15.0" /> <PackageReference Include="DotNetZip" Version="1.15.0" />
<PackageReference Include="EntityFramework" Version="6.4.4" /> <PackageReference Include="EntityFramework" Version="6.4.4" />
<PackageReference Include="ID3" Version="0.6.0" /> <PackageReference Include="ID3" Version="0.6.0" />
<PackageReference Include="JWT" Version="9.0.3" /> <PackageReference Include="JWT" Version="10.1.1" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.1" /> <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.6" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="6.0.1" /> <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="5.0.8" /> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="5.0.8"> <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.6">
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
</PackageReference> </PackageReference>
<PackageReference Include="MySql.EntityFrameworkCore" Version="5.0.8" /> <PackageReference Include="MySql.EntityFrameworkCore" Version="8.0.2" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="NLog.Web.AspNetCore" Version="4.14.0" /> <PackageReference Include="NLog.Web.AspNetCore" Version="4.14.0" />
<PackageReference Include="Portable.BouncyCastle" Version="1.9.0" />
<PackageReference Include="RestSharp" Version="106.15.0" /> <PackageReference Include="RestSharp" Version="106.15.0" />
<PackageReference Include="SevenZip" Version="19.0.0" /> <PackageReference Include="SevenZip" Version="19.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" /> <PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.15.0" /> <PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.6.0" />
<PackageReference Include="system.text.encoding.codepages" Version="8.0.0" />
<PackageReference Include="taglib" Version="2.1.0" /> <PackageReference Include="taglib" Version="2.1.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
-6
View File
@@ -1,5 +1,3 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using Newtonsoft.Json; using Newtonsoft.Json;
@@ -20,8 +18,4 @@ public class Album
public int SongCount { get; set; } public int SongCount { get; set; }
[JsonProperty("year")] [JsonProperty("year")]
public int Year { get; set; } public int Year { get; set; }
[JsonIgnore]
[NotMapped]
public List<Song> Songs { get; set; }
} }
-5
View File
@@ -1,5 +1,3 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using Newtonsoft.Json; using Newtonsoft.Json;
@@ -16,7 +14,4 @@ public class Artist
[JsonProperty("song_count")] [JsonProperty("song_count")]
[NotMapped] [NotMapped]
public int SongCount { get; set; } public int SongCount { get; set; }
[JsonIgnore]
[NotMapped]
public List<Song> Songs { get; set; }
} }
+4 -9
View File
@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using System.Linq; using System.Linq;
using System.Reflection.Metadata;
using System.Text; using System.Text;
using Newtonsoft.Json; using Newtonsoft.Json;
@@ -17,24 +18,18 @@ public class CoverArt
public string SongTitle { get; set; } public string SongTitle { get; set; }
[JsonIgnore] [JsonIgnore]
public string ImagePath { get; set; } public string ImagePath { get; set; }
[JsonIgnore]
[NotMapped]
public int SongID { get; set; }
[JsonIgnore]
[NotMapped]
public List<Song> Songs { get; set; }
#endregion #endregion
#region Methods #region Methods
public string GenerateFilename(int flag) public string GenerateFilename(int flag)
{ {
const int length = 25; int length = Constants.DirectoryPaths.FILENAME_LENGTH;
const string chars = "ABCDEF0123456789"; string chars = Constants.DirectoryPaths.FILENAME_CHARACTERS;
var random = new Random(); var random = new Random();
var filename = new string(Enumerable.Repeat(chars, length).Select(s => var filename = new string(Enumerable.Repeat(chars, length).Select(s =>
s[random.Next(s.Length)]).ToArray()); s[random.Next(s.Length)]).ToArray());
var extension = ".mp3"; var extension = Constants.FileExtensions.JPG_EXTENSION;
return (flag == 0) ? filename : $"{filename}{extension}"; return (flag == 0) ? filename : $"{filename}{extension}";
} }
-5
View File
@@ -1,5 +1,3 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using Newtonsoft.Json; using Newtonsoft.Json;
@@ -16,7 +14,4 @@ public class Genre
[JsonProperty("song_count")] [JsonProperty("song_count")]
[NotMapped] [NotMapped]
public int SongCount { get; set; } public int SongCount { get; set; }
[JsonIgnore]
[NotMapped]
public List<Song> Songs { get; set; }
} }
+6 -6
View File
@@ -73,20 +73,20 @@ public class Song
public string GenerateFilename(int flag = 0) public string GenerateFilename(int flag = 0)
{ {
const int length = 25; int length = Constants.DirectoryPaths.FILENAME_LENGTH;
const string chars = "ABCDEF0123456789"; string chars = Constants.DirectoryPaths.FILENAME_CHARACTERS;
var random = new Random(); var random = new Random();
var filename = new string(Enumerable.Repeat(chars, length).Select(s => var filename = new string(Enumerable.Repeat(chars, length).Select(s =>
s[random.Next(s.Length)]).ToArray()); s[random.Next(s.Length)]).ToArray());
var extension = ".mp3"; var extension = Icarus.Constants.FileExtensions.WAV_EXTENSION;
return flag == 0 ? filename : $"{filename}{extension}"; return flag == 0 ? filename : $"{filename}{extension}";
} }
public async Task<string> GenerateFilenameAsync(int flag = 0) public async Task<string> GenerateFilenameAsync(int flag = 0)
{ {
const int length = 25; int length = Constants.DirectoryPaths.FILENAME_LENGTH;
const string chars = "ABCDEF0123456789"; string chars = Constants.DirectoryPaths.FILENAME_CHARACTERS;
var extension = ".mp3"; var extension = Icarus.Constants.FileExtensions.WAV_EXTENSION;
var random = new Random(); var random = new Random();
var filename = await Task.Run(() => var filename = await Task.Run(() =>
{ {
+96 -36
View File
@@ -1,45 +1,105 @@
using System; using Microsoft.AspNetCore.Authentication.JwtBearer;
using System.Collections.Generic; using Microsoft.EntityFrameworkCore;
using System.IO; using Microsoft.IdentityModel.Tokens;
using System.Linq; using Microsoft.OpenApi.Models;
using System.Threading.Tasks;
using Microsoft.AspNetCore; using Icarus.Database.Contexts;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using NLog.Web;
namespace Icarus;
public class Program var builder = WebApplication.CreateBuilder(args);
{
public static void Main(string[] args)
{
var logger = NLog.Web.NLogBuilder.ConfigureNLog("nlog.config").GetCurrentClassLogger();
try var MAX_REQUEST_BODY_SIZE = 51200000000;
{
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) => builder.Services.AddEndpointsApiExplorer();
Host.CreateDefaultBuilder(args) builder.Services.AddSwaggerGen(c =>
.ConfigureWebHostDefaults(webBuilder =>
{ {
webBuilder.UseStartup<Startup>(); c.ResolveConflictingActions(apiDescriptions => apiDescriptions.First());
c.SwaggerDoc("v1", new OpenApiInfo { Title = "Icarus", Version = "v1" });
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme()
{
Name = "Authorization",
Scheme = "Bearer",
BearerFormat = "JWT",
Type = SecuritySchemeType.ApiKey,
In = ParameterLocation.Header,
Description = "Bearer *Auth Token*",
}); });
c.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
} }
},
new string[] {}
}
});
});
builder.Services.AddControllers();
builder.WebHost.UseKestrel(option =>
{
option.Limits.MaxRequestBodySize = MAX_REQUEST_BODY_SIZE;
});
var Configuration = builder.Configuration;
var connString = Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options =>
{
options.RequireHttpsMetadata = false;
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateIssuerSigningKey = true,
ValidateLifetime = true,
ValidAudience = Configuration["JWT:Audience"],
ValidIssuer = Configuration["JWT:Issuer"],
IssuerSigningKey = new SymmetricSecurityKey(System.Text.Encoding.UTF8.GetBytes(Configuration["JWT:Secret"]))
};
});
builder.Services.AddDbContext<SongContext>(options => options.UseMySQL(connString));
builder.Services.AddDbContext<AlbumContext>(options => options.UseMySQL(connString));
builder.Services.AddDbContext<ArtistContext>(options => options.UseMySQL(connString));
builder.Services.AddDbContext<UserContext>(options => options.UseMySQL(connString));
builder.Services.AddDbContext<GenreContext>(options => options.UseMySQL(connString));
builder.Services.AddDbContext<CoverArtContext>(options => options.UseMySQL(connString));
builder.Services.AddControllers()
.AddNewtonsoftJson();
builder.Services.AddEndpointsApiExplorer();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
// NOTE: This should be enabled at some point
// app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
app.Run();
+5 -5
View File
@@ -1,18 +1,18 @@
# Icarus # Icarus
Icarus is a music streaming API Server that interacts with [Mear](https://github.com/amazing-username/mear). Icarus is a music streaming API Server that interacts with [Mear](https://github.com/kdeng00/mear).
### Interfacing With Icarus ### Interfacing With Icarus
One can interface with Icarus the music server either by: 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/kdeng00/mear) - Partially implemented (under development)
* [IcarusDownloadManager](https://github.com/amazing-username/IcarusDownloadManager) * [IcarusDownloadManager](https://github.com/kdeng00/IcarusDownloadManager)
## Built With ## Built With
* C# [.NET](https://dotnet.microsoft.com/) 6 * C# [.NET](https://dotnet.microsoft.com/) 8
* [MySql](https://www.nuget.org/packages/MySql.Data/) * [MySql](https://www.nuget.org/packages/MySql.Data/)
* OpenSSL * OpenSSL
* BCrypt.Net-Next * BCrypt.Net-Next
@@ -120,7 +120,7 @@ Prior to starting the API, the Migrations must be applied. There are 6 tables wi
* Year * Year
* Genre * Genre
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: There is a script for Linux systems to apply these migrations, it can be found in the [Scripts/Migrations/Linux](https://github.com/kdeng00/Icarus/blob/master/Scripts/Migrations/Linux/AddUpdate.sh) directory. Just merely execute:
```shell ```shell
scripts/Migrations/Linux/AddUpdate.sh scripts/Migrations/Linux/AddUpdate.sh
``` ```
-125
View File
@@ -1,125 +0,0 @@
using System;
using System.Text;
using System.Linq;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
using Newtonsoft.Json;
using NLog;
using NLog.Web;
using NLog.Web.AspNetCore;
using Icarus.Database.Contexts;
namespace Icarus;
public class Startup
{
#region Constructors
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
#endregion
#region Properties
public IConfiguration Configuration { get; }
#endregion
#region Methods
// 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"];
var connString = Configuration.GetConnectionString("DefaultConnection");
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options =>
{
options.RequireHttpsMetadata = false;
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = true,
ValidateAudience = true,
ValidAudience = Configuration["JWT:Audience"],
ValidIssuer = Configuration["JWT:Issuer"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["JWT:Secret"]))
};
});
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();
services.AddEndpointsApiExplorer();
services.AddSwaggerGen(c =>
{
c.ResolveConflictingActions(apiDescriptions => apiDescriptions.First());
c.SwaggerDoc("v1", new OpenApiInfo { Title = "Icarus", Version = "v1" });
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme()
{
Name = "Authorization",
Scheme = "Bearer",
BearerFormat = "JWT",
Type = SecuritySchemeType.ApiKey,
In = ParameterLocation.Header,
Description = "Bearer *Auth Token*",
});
c.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
new string[] {}
}
});
});
}
// 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
if (env.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
#endregion
}