Net8 (#89)
* .NET 8 migration * Fixed bugs with song management * More cleanup and making some constants * Updated yml * Clean up Removing comments, some cleanup, and moving the startup code into Program.cs * Removing comments * Fixed song deletion issue * Added functionality to delete song directories * Updated README
This commit was merged in pull request #89.
This commit is contained in:
@@ -16,7 +16,7 @@ jobs:
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v2
|
||||
with:
|
||||
dotnet-version: 6.0.x
|
||||
dotnet-version: 8.0.x
|
||||
- name: Restore dependencies
|
||||
run: dotnet restore
|
||||
- name: Build
|
||||
|
||||
+6
-7
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using Org.BouncyCastle.Crypto.Parameters;
|
||||
@@ -10,11 +9,11 @@ namespace Icarus.Certs;
|
||||
|
||||
public class SigningIssuerCertificate : IDisposable
|
||||
{
|
||||
private readonly RSACryptoServiceProvider _rsa;
|
||||
private readonly System.Security.Cryptography.RSACryptoServiceProvider _rsa;
|
||||
|
||||
public SigningIssuerCertificate()
|
||||
{
|
||||
_rsa = new RSACryptoServiceProvider();
|
||||
_rsa = new System.Security.Cryptography.RSACryptoServiceProvider();
|
||||
}
|
||||
|
||||
public RsaSecurityKey GetIssuerSigningKey(string publicKeyPath)
|
||||
@@ -26,7 +25,7 @@ public class SigningIssuerCertificate : IDisposable
|
||||
{
|
||||
var pem = new PemReader(reader);
|
||||
var o = (RsaKeyParameters)pem.ReadObject();
|
||||
var parameters = new RSAParameters();
|
||||
var parameters = new System.Security.Cryptography.RSAParameters();
|
||||
parameters.Modulus = o.Modulus.ToByteArray();
|
||||
parameters.Exponent = o.Exponent.ToByteArray();
|
||||
_rsa.ImportParameters(parameters);
|
||||
@@ -45,11 +44,11 @@ public class SigningIssuerCertificate : IDisposable
|
||||
|
||||
public class SigningAudienceCertificate : IDisposable
|
||||
{
|
||||
private readonly RSACryptoServiceProvider _rsa;
|
||||
private readonly System.Security.Cryptography.RSACryptoServiceProvider _rsa;
|
||||
|
||||
public SigningAudienceCertificate()
|
||||
{
|
||||
_rsa = new RSACryptoServiceProvider();
|
||||
_rsa = new System.Security.Cryptography.RSACryptoServiceProvider();
|
||||
}
|
||||
|
||||
public SigningCredentials GetAudienceSigningKey(string keyPath)
|
||||
@@ -61,7 +60,7 @@ public class SigningAudienceCertificate : IDisposable
|
||||
{
|
||||
var pem = new PemReader(reader);
|
||||
var o = (RsaKeyParameters)pem.ReadObject();
|
||||
var parameters = new RSAParameters();
|
||||
var parameters = new System.Security.Cryptography.RSAParameters();
|
||||
parameters.Modulus = o.Modulus.ToByteArray();
|
||||
parameters.Exponent = o.Exponent.ToByteArray();
|
||||
_rsa.ImportParameters(parameters);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Icarus.Constants;
|
||||
@@ -7,4 +6,6 @@ public class DirectoryPaths
|
||||
{
|
||||
public static string CoverArtPath =>
|
||||
Directory.GetCurrentDirectory() + "/Images/Stock/CoverArt.png";
|
||||
public static string FILENAME_CHARACTERS = "ABCDEF0123456789";
|
||||
public static int FILENAME_LENGTH = 25;
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
@@ -47,6 +47,7 @@ public class CoverArtManager : BaseManager
|
||||
{
|
||||
_logger.Info("Attempting to delete cover art from the database");
|
||||
|
||||
_coverArtContext.Attach(coverArt);
|
||||
_coverArtContext.Remove(coverArt);
|
||||
_coverArtContext.SaveChanges();
|
||||
}
|
||||
|
||||
@@ -145,7 +145,7 @@ public class SongManager : BaseManager
|
||||
{
|
||||
try
|
||||
{
|
||||
if (DeleteSongFromFilesystem(song))
|
||||
if (DeleteSongFromFilesystem(song, true))
|
||||
{
|
||||
_logger.Error("Failed to delete the song");
|
||||
|
||||
@@ -158,8 +158,8 @@ public class SongManager : BaseManager
|
||||
var coverArt = coverMgr.GetCoverArt(song);
|
||||
coverMgr.DeleteCoverArt(coverArt);
|
||||
|
||||
coverMgr.DeleteCoverArtFromDatabase(coverArt);
|
||||
DeleteSongFromDatabase(song);
|
||||
coverMgr.DeleteCoverArtFromDatabase(coverArt);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -241,13 +241,7 @@ public class SongManager : BaseManager
|
||||
}
|
||||
|
||||
var coverMgr = new CoverArtManager(_config);
|
||||
var meta = new MetadataRetriever();
|
||||
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);
|
||||
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();
|
||||
|
||||
@@ -415,6 +409,8 @@ public class SongManager : BaseManager
|
||||
try
|
||||
{
|
||||
System.IO.File.Delete(songPath);
|
||||
|
||||
DeleteEmptyDirectories(ref song, ref song);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
@@ -425,6 +421,7 @@ public class SongManager : BaseManager
|
||||
|
||||
return DoesSongExistOnFilesystem(song);
|
||||
}
|
||||
|
||||
private bool DoesSongExistOnFilesystem(Song song)
|
||||
{
|
||||
if (!System.IO.File.Exists(song.SongPath()))
|
||||
@@ -494,13 +491,12 @@ public class SongManager : BaseManager
|
||||
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();
|
||||
artistMgr.DeleteArtistFromDatabase(song);
|
||||
albumMgr.DeleteAlbumFromDatabase(song);
|
||||
genreMgr.DeleteGenreFromDatabase(song);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -2,20 +2,13 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading.Tasks;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Text;
|
||||
|
||||
using JWT;
|
||||
using JWT.Serializers;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using Newtonsoft.Json;
|
||||
using Org.BouncyCastle.Crypto;
|
||||
using Org.BouncyCastle.Crypto.Parameters;
|
||||
using Org.BouncyCastle.OpenSsl;
|
||||
using Org.BouncyCastle.Security;
|
||||
using RestSharp;
|
||||
|
||||
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)
|
||||
{
|
||||
var tokenResult = new TokenTierOne();
|
||||
@@ -124,16 +88,25 @@ public class TokenManager : BaseManager
|
||||
var payload = Payload();
|
||||
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 signIn = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
|
||||
var token = new JwtSecurityToken(
|
||||
_config["JWT:Issuer"],
|
||||
_config["JWT:Audience"],
|
||||
payload,
|
||||
expires: DateTime.UtcNow.AddMinutes(30),
|
||||
signingCredentials: signIn);
|
||||
var tokenHandler = new JwtSecurityTokenHandler();
|
||||
var key = Encoding.ASCII.GetBytes(_config["JWT:Secret"]);
|
||||
var tokenDescriptor = new SecurityTokenDescriptor
|
||||
{
|
||||
Subject = new ClaimsIdentity(new Claim[]
|
||||
{
|
||||
new Claim("user_id", user.UserID.ToString(), ClaimValueTypes.Integer)
|
||||
// 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 =>
|
||||
{
|
||||
@@ -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()
|
||||
{
|
||||
@@ -266,81 +186,6 @@ public class TokenManager : BaseManager
|
||||
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)
|
||||
{
|
||||
return await System.IO.File.ReadAllTextAsync(filepath);
|
||||
|
||||
@@ -83,7 +83,7 @@ public class SongCompression
|
||||
Console.WriteLine(exMsg);
|
||||
}
|
||||
|
||||
if (songDetails.Filename.Contains(".mp3"))
|
||||
if (songDetails.Filename.Contains(Constants.FileExtensions.WAV_EXTENSION))
|
||||
{
|
||||
_compressedSongFilename = StripMP3Extension(songDetails.Filename);
|
||||
}
|
||||
|
||||
@@ -4,8 +4,6 @@ using System.Linq;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
using Icarus.Controllers.Managers;
|
||||
|
||||
|
||||
namespace Icarus.Controllers.V1;
|
||||
|
||||
@@ -39,14 +37,5 @@ public class BaseController : ControllerBase
|
||||
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
|
||||
}
|
||||
|
||||
@@ -148,7 +148,7 @@ public class SongDataController : BaseController
|
||||
|
||||
_songMgr.DeleteSong(songMetaData);
|
||||
|
||||
return Ok();
|
||||
return Ok(songMetaData);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -53,7 +53,12 @@ public class SongStreamController : BaseController
|
||||
|
||||
var stream = new FileStream(song.SongPath(), FileMode.Open, FileAccess.Read);
|
||||
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...>");
|
||||
Console.WriteLine("Starting to streamsong...");
|
||||
|
||||
@@ -25,38 +25,6 @@ public class SongContext : DbContext
|
||||
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);
|
||||
|
||||
@@ -2,11 +2,6 @@ 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 Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
|
||||
+12
-10
@@ -1,32 +1,34 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
|
||||
<Version>0.1.10</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<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="EntityFramework" Version="6.4.4" />
|
||||
<PackageReference Include="ID3" Version="0.6.0" />
|
||||
<PackageReference Include="JWT" Version="9.0.3" />
|
||||
<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">
|
||||
<PackageReference Include="JWT" Version="10.1.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.6" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.6" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.6" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.6">
|
||||
<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.2" />
|
||||
<PackageReference Include="MySql.EntityFrameworkCore" Version="8.0.2" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<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="SevenZip" Version="19.0.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" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
@@ -20,8 +18,4 @@ public class Album
|
||||
public int SongCount { get; set; }
|
||||
[JsonProperty("year")]
|
||||
public int Year { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
[NotMapped]
|
||||
public List<Song> Songs { get; set; }
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
@@ -16,7 +14,4 @@ public class Artist
|
||||
[JsonProperty("song_count")]
|
||||
[NotMapped]
|
||||
public int SongCount { get; set; }
|
||||
[JsonIgnore]
|
||||
[NotMapped]
|
||||
public List<Song> Songs { get; set; }
|
||||
}
|
||||
|
||||
+4
-9
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Linq;
|
||||
using System.Reflection.Metadata;
|
||||
using System.Text;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
@@ -17,24 +18,18 @@ public class CoverArt
|
||||
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";
|
||||
int length = Constants.DirectoryPaths.FILENAME_LENGTH;
|
||||
string chars = Constants.DirectoryPaths.FILENAME_CHARACTERS;
|
||||
var random = new Random();
|
||||
var filename = new string(Enumerable.Repeat(chars, length).Select(s =>
|
||||
s[random.Next(s.Length)]).ToArray());
|
||||
var extension = ".mp3";
|
||||
var extension = Constants.FileExtensions.JPG_EXTENSION;
|
||||
|
||||
return (flag == 0) ? filename : $"{filename}{extension}";
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
@@ -16,7 +14,4 @@ public class Genre
|
||||
[JsonProperty("song_count")]
|
||||
[NotMapped]
|
||||
public int SongCount { get; set; }
|
||||
[JsonIgnore]
|
||||
[NotMapped]
|
||||
public List<Song> Songs { get; set; }
|
||||
}
|
||||
|
||||
+6
-6
@@ -73,20 +73,20 @@ public class Song
|
||||
|
||||
public string GenerateFilename(int flag = 0)
|
||||
{
|
||||
const int length = 25;
|
||||
const string chars = "ABCDEF0123456789";
|
||||
int length = Constants.DirectoryPaths.FILENAME_LENGTH;
|
||||
string chars = Constants.DirectoryPaths.FILENAME_CHARACTERS;
|
||||
var random = new Random();
|
||||
var filename = new string(Enumerable.Repeat(chars, length).Select(s =>
|
||||
s[random.Next(s.Length)]).ToArray());
|
||||
var extension = ".mp3";
|
||||
var extension = Icarus.Constants.FileExtensions.WAV_EXTENSION;
|
||||
|
||||
return flag == 0 ? filename : $"{filename}{extension}";
|
||||
}
|
||||
public async Task<string> GenerateFilenameAsync(int flag = 0)
|
||||
{
|
||||
const int length = 25;
|
||||
const string chars = "ABCDEF0123456789";
|
||||
var extension = ".mp3";
|
||||
int length = Constants.DirectoryPaths.FILENAME_LENGTH;
|
||||
string chars = Constants.DirectoryPaths.FILENAME_CHARACTERS;
|
||||
var extension = Icarus.Constants.FileExtensions.WAV_EXTENSION;
|
||||
var random = new Random();
|
||||
var filename = await Task.Run(() =>
|
||||
{
|
||||
|
||||
+97
-37
@@ -1,45 +1,105 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using Microsoft.OpenApi.Models;
|
||||
|
||||
using Microsoft.AspNetCore;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NLog.Web;
|
||||
using Icarus.Database.Contexts;
|
||||
|
||||
namespace Icarus;
|
||||
|
||||
public class Program
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
var MAX_REQUEST_BODY_SIZE = 51200000000;
|
||||
|
||||
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
builder.Services.AddSwaggerGen(c =>
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
var logger = NLog.Web.NLogBuilder.ConfigureNLog("nlog.config").GetCurrentClassLogger();
|
||||
c.ResolveConflictingActions(apiDescriptions => apiDescriptions.First());
|
||||
|
||||
try
|
||||
c.SwaggerDoc("v1", new OpenApiInfo { Title = "Icarus", Version = "v1" });
|
||||
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme()
|
||||
{
|
||||
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>();
|
||||
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();
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
# 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
|
||||
|
||||
One can interface with Icarus the music server either by:
|
||||
|
||||
* [Mear](https://github.com/amazing-username/mear) - Partially implemented (under development)
|
||||
* [IcarusDownloadManager](https://github.com/amazing-username/IcarusDownloadManager)
|
||||
* [Mear](https://github.com/kdeng00/mear) - Partially implemented (under development)
|
||||
* [IcarusDownloadManager](https://github.com/kdeng00/IcarusDownloadManager)
|
||||
|
||||
|
||||
## Built With
|
||||
|
||||
* C# [.NET](https://dotnet.microsoft.com/) 6
|
||||
* C# [.NET](https://dotnet.microsoft.com/) 8
|
||||
* [MySql](https://www.nuget.org/packages/MySql.Data/)
|
||||
* OpenSSL
|
||||
* BCrypt.Net-Next
|
||||
@@ -120,7 +120,7 @@ Prior to starting the API, the Migrations must be applied. There are 6 tables wi
|
||||
* Year
|
||||
* 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
|
||||
scripts/Migrations/Linux/AddUpdate.sh
|
||||
```
|
||||
|
||||
-125
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user