#74: Add UserID to the Song model #98

Merged
kdeng00 merged 5 commits from tsk-74 into master 2024-06-19 15:41:56 -04:00
25 changed files with 117 additions and 164 deletions
-2
View File
@@ -1,5 +1,3 @@
using System.IO;
namespace Icarus.Constants; namespace Icarus.Constants;
public class DirectoryPaths public class DirectoryPaths
+6 -6
View File
@@ -29,13 +29,13 @@ public class ArtistManager : BaseManager
{ {
_logger.Info("Starting process to save the artist record of the song to the database"); _logger.Info("Starting process to save the artist record of the song to the database");
var artist = new Artist(); var artist = new Artist
{
Name = song.Artist,
SongCount = 1
};
artist.Name = song.Artist; var artistRetrieved = _artistContext.Artists.FirstOrDefault(art => art.Name.Equals(artist.Name));
artist.SongCount = 1;
var artistTitle = artist.Name;
var artistRetrieved = _artistContext.Artists.FirstOrDefault(art => art.Name.Equals(artistTitle));
if (artistRetrieved == null) if (artistRetrieved == null)
{ {
+1 -1
View File
@@ -97,7 +97,7 @@ public class CoverArtManager : BaseManager
else else
{ {
_logger.Info("Song has no cover art, applying stock cover art"); _logger.Info("Song has no cover art, applying stock cover art");
// coverArt.ImagePath = _rootCoverArtPath + $"{segment}{defaultExtension}";
var coverArtFilePath = _rootCoverArtPath + $"{segment}{defaultExtension}"; var coverArtFilePath = _rootCoverArtPath + $"{segment}{defaultExtension}";
coverArt.ImagePath = DirectoryPaths.CoverArtPath; coverArt.ImagePath = DirectoryPaths.CoverArtPath;
metaData.UpdateCoverArt(song, coverArt); metaData.UpdateCoverArt(song, coverArt);
+1 -1
View File
@@ -107,7 +107,7 @@ public class SongManager : BaseManager
try try
{ {
var songPath = songMetaData.SongPath(); var songPath = songMetaData.SongPath();
System.IO.File.Delete(songPath); File.Delete(songPath);
successful = true; successful = true;
DirectoryManager dirMgr = new DirectoryManager(_config, songMetaData); DirectoryManager dirMgr = new DirectoryManager(_config, songMetaData);
dirMgr.DeleteEmptyDirectories(); dirMgr.DeleteEmptyDirectories();
+50 -18
View File
@@ -7,6 +7,7 @@ using Newtonsoft.Json;
using RestSharp; using RestSharp;
using Icarus.Models; using Icarus.Models;
using Microsoft.VisualBasic;
namespace Icarus.Controllers.Managers; namespace Icarus.Controllers.Managers;
@@ -77,11 +78,10 @@ public class TokenManager : BaseManager
public LoginResult LoginSymmetric(User user) public LoginResult LoginSymmetric(User user)
{ {
var tokenResult = new TokenTierOne(); var tokenResult = new TokenTierOne{ TokenType = "JWT" };
tokenResult.TokenType = "Jwt";
var payload = Payload(); var payload = Payload();
payload.Add(new System.Security.Claims.Claim("user_id", user.UserID.ToString(), ClaimValueTypes.Integer)); payload.Add(new Claim("user_id", user.UserID.ToString(), ClaimValueTypes.Integer));
var tokenHandler = new JwtSecurityTokenHandler(); var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_config["JWT:Secret"]); var key = Encoding.ASCII.GetBytes(_config["JWT:Secret"]);
@@ -98,10 +98,7 @@ public class TokenManager : BaseManager
Audience = _config["Jwt:Audience"] Audience = _config["Jwt:Audience"]
}; };
var token = tokenHandler.CreateToken(tokenDescriptor); tokenResult.AccessToken = tokenHandler.WriteToken(tokenHandler.CreateToken(tokenDescriptor));
tokenResult.AccessToken = tokenHandler.WriteToken(token);
var expClaim = payload.FirstOrDefault(cl => var expClaim = payload.FirstOrDefault(cl =>
{ {
@@ -120,6 +117,42 @@ public class TokenManager : BaseManager
}; };
} }
public int RetrieveUserIdFromToken(string token)
{
if (this.ContainsBearer(token))
{
token = this.StripBearer(token);
}
var tokenHandler = new JwtSecurityTokenHandler();
var readTok = tokenHandler.ReadJwtToken(token);
var userId = -1;
foreach (var item in readTok.Payload)
{
if (item.Key == "user_id")
{
userId = Convert.ToInt32(item.Value);
break;
}
}
return userId;
}
private string StripBearer(string token)
{
var start = 6;
var strippedToken = token.Substring(start);
return Strings.Trim(strippedToken);
}
private bool ContainsBearer(string token)
{
return token.Contains("Bearer");
}
private string AllScopes() private string AllScopes()
{ {
@@ -157,30 +190,28 @@ public class TokenManager : BaseManager
private List<Claim> Payload() private List<Claim> Payload()
{ {
// TODO: Remove this hard coding
var expLimit = 30; var expLimit = 30;
var currentDate = DateTime.Now; var currentDate = DateTime.Now;
var expiredDate = currentDate.AddMinutes(expLimit); var expiredDate = currentDate.AddMinutes(expLimit);
var issued = Math.Floor((currentDate - DateTime.UnixEpoch).TotalSeconds); var issuer = "http://localhost:5002";
var expires = Math.Floor((expiredDate - DateTime.UnixEpoch).TotalSeconds); var audience = "http://localhost:5002";
var issuer = "https://soaricarus.auth0.com";
issuer = "http://localhost:5002";
var audience = "https://icarus/api";
audience = "http://localhost:5002";
var subject = _config["JWT:Subject"]; var subject = _config["JWT:Subject"];
var claim = new List<System.Security.Claims.Claim>() var claim = new List<System.Security.Claims.Claim>()
{ {
new System.Security.Claims.Claim("scope", AllScopes(), "string"), new Claim("scope", AllScopes(), "string"),
new System.Security.Claims.Claim(JwtRegisteredClaimNames.Exp, expiredDate.ToString()), new Claim(JwtRegisteredClaimNames.Exp, expiredDate.ToString()),
new System.Security.Claims.Claim(JwtRegisteredClaimNames.Aud, audience), new Claim(JwtRegisteredClaimNames.Aud, audience),
new System.Security.Claims.Claim(JwtRegisteredClaimNames.Iss, issuer), new Claim(JwtRegisteredClaimNames.Iss, issuer),
new Claim(JwtRegisteredClaimNames.Sub, subject), new Claim(JwtRegisteredClaimNames.Sub, subject),
new System.Security.Claims.Claim(JwtRegisteredClaimNames.Iat, currentDate.ToString()) new Claim(JwtRegisteredClaimNames.Iat, currentDate.ToString())
}; };
return claim; return claim;
} }
[Obsolete("Deprecated function")]
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);
@@ -234,6 +265,7 @@ public class TokenManager : BaseManager
[JsonProperty("grant_type")] [JsonProperty("grant_type")]
public string GrantType { get; set; } public string GrantType { get; set; }
} }
private class TokenTierOne private class TokenTierOne
{ {
[JsonProperty("access_token")] [JsonProperty("access_token")]
-5
View File
@@ -1,9 +1,4 @@
using System;
using System.IO;
using System.Threading.Tasks;
using Ionic.Zip; using Ionic.Zip;
using Microsoft.AspNetCore.Http;
using Icarus.Models; using Icarus.Models;
+2 -13
View File
@@ -1,11 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Icarus.Models; using Icarus.Models;
using Icarus.Database.Contexts; using Icarus.Database.Contexts;
@@ -41,11 +35,9 @@ public class AlbumController : BaseController
[HttpGet] [HttpGet]
public IActionResult GetAlbums() public IActionResult GetAlbums()
{ {
List<Album> albums = new List<Album>();
var albumContext = new AlbumContext(_connectionString); var albumContext = new AlbumContext(_connectionString);
albums = albumContext.Albums.ToList(); var albums = albumContext.Albums.ToList();
if (albums.Count > 0) if (albums.Count > 0)
return Ok(albums); return Ok(albums);
@@ -56,10 +48,7 @@ public class AlbumController : BaseController
[HttpGet("{id}")] [HttpGet("{id}")]
public IActionResult GetAlbum(int id) public IActionResult GetAlbum(int id)
{ {
Album album = new Album Album album = new Album{ AlbumID = id };
{
AlbumID = id
};
var albumContext = new AlbumContext(_connectionString); var albumContext = new AlbumContext(_connectionString);
+2 -5
View File
@@ -1,10 +1,5 @@
using System;
using System.Linq;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Icarus.Models; using Icarus.Models;
using Icarus.Database.Contexts; using Icarus.Database.Contexts;
@@ -67,7 +62,9 @@ public class ArtistController : BaseController
return Ok(artist); return Ok(artist);
} }
else else
{
return NotFound(); return NotFound();
}
} }
#endregion #endregion
} }
-4
View File
@@ -1,8 +1,4 @@
using System;
using System.Linq;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
namespace Icarus.Controllers.V1; namespace Icarus.Controllers.V1;
+8 -13
View File
@@ -1,11 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Icarus.Models; using Icarus.Models;
using Icarus.Database.Contexts; using Icarus.Database.Contexts;
@@ -41,25 +35,24 @@ public class GenreController : BaseController
[HttpGet] [HttpGet]
public IActionResult GetGenres() public IActionResult GetGenres()
{ {
var genres = new List<Genre>();
var genreStore = new GenreContext(_connectionString); var genreStore = new GenreContext(_connectionString);
genres = genreStore.Genres.ToList(); var genres = genreStore.Genres.ToList();
if (genres.Count > 0) if (genres.Count > 0)
{
return Ok(genres); return Ok(genres);
}
else else
{
return NotFound(new List<Genre>()); return NotFound(new List<Genre>());
}
} }
[HttpGet("{id}")] [HttpGet("{id}")]
public IActionResult GetGenre(int id) public IActionResult GetGenre(int id)
{ {
var genre = new Genre var genre = new Genre{ GenreID = id };
{
GenreID = id
};
var genreStore = new GenreContext(_connectionString); var genreStore = new GenreContext(_connectionString);
@@ -70,7 +63,9 @@ public class GenreController : BaseController
return Ok(genre); return Ok(genre);
} }
else else
{
return NotFound(new Genre()); return NotFound(new Genre());
}
} }
#endregion #endregion
} }
View File
-10
View File
@@ -1,13 +1,5 @@
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Icarus.Controllers.Managers;
using Icarus.Controllers.Utilities; using Icarus.Controllers.Utilities;
using Icarus.Models; using Icarus.Models;
using Icarus.Database.Contexts; using Icarus.Database.Contexts;
@@ -19,7 +11,6 @@ namespace Icarus.Controllers.V1;
public class RegisterController : ControllerBase public class RegisterController : ControllerBase
{ {
#region Fields #region Fields
private string _connectionString;
private IConfiguration _config; private IConfiguration _config;
#endregion #endregion
@@ -32,7 +23,6 @@ public class RegisterController : ControllerBase
public RegisterController(IConfiguration config) public RegisterController(IConfiguration config)
{ {
_config = config; _config = config;
_connectionString = _config.GetConnectionString("DefaultConnection");
} }
#endregion #endregion
@@ -42,7 +42,6 @@ public class SongCompressedDataController : BaseController
var context = new SongContext(_connectionString); var context = new SongContext(_connectionString);
SongCompression cmp = new SongCompression(_archiveDir); SongCompression cmp = new SongCompression(_archiveDir);
Console.WriteLine($"Archive directory root: {_archiveDir}"); Console.WriteLine($"Archive directory root: {_archiveDir}");
+8 -19
View File
@@ -1,16 +1,7 @@
using System; using Microsoft.AspNetCore.Authorization;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Icarus.Controllers.Managers; using Icarus.Controllers.Managers;
using Icarus.Controllers.Utilities;
using Icarus.Models; using Icarus.Models;
using Icarus.Database.Contexts; using Icarus.Database.Contexts;
@@ -36,8 +27,8 @@ public class SongController : BaseController
public SongController(IConfiguration config, ILogger<SongController> logger) public SongController(IConfiguration config, ILogger<SongController> logger)
{ {
_config = config; _config = config;
_connectionString = _config.GetConnectionString("DefaultConnection");
_logger = logger; _logger = logger;
_connectionString = _config.GetConnectionString("DefaultConnection");
_songMgr = new SongManager(config); _songMgr = new SongManager(config);
} }
#endregion #endregion
@@ -45,23 +36,24 @@ public class SongController : BaseController
#region Methods #region Methods
#region HTTP Endpoints #region HTTP Endpoints
[HttpGet] [HttpGet]
public IActionResult GetSongs() public IActionResult GetSongs()
{ {
List<Song> songs = new List<Song>();
Console.WriteLine("Attemtping to retrieve songs"); Console.WriteLine("Attemtping to retrieve songs");
_logger.LogInformation("Attempting to retrieve songs"); _logger.LogInformation("Attempting to retrieve songs");
var context = new SongContext(_connectionString); var context = new SongContext(_connectionString);
songs = context.Songs.ToList(); var songs = context.Songs.ToList();
if (songs.Count > 0) if (songs.Count > 0)
{
return Ok(songs); return Ok(songs);
}
else else
{
return NotFound(); return NotFound();
}
} }
[HttpGet("{id}")] [HttpGet("{id}")]
@@ -69,8 +61,7 @@ public class SongController : BaseController
{ {
var context = new SongContext(_connectionString); var context = new SongContext(_connectionString);
Song song = new Song { SongID = id }; var song = context.RetrieveRecord(new Song{ SongID = id });
song = context.RetrieveRecord(song);
Console.WriteLine("Here"); Console.WriteLine("Here");
@@ -83,8 +74,6 @@ public class SongController : BaseController
[HttpPut("{id}")] [HttpPut("{id}")]
public IActionResult UpdateSong(int id, [FromBody] Song song) public IActionResult UpdateSong(int id, [FromBody] Song song)
{ {
var context = new SongContext(_connectionString);
song.SongID = id; song.SongID = id;
Console.WriteLine("Retrieving filepath of song"); Console.WriteLine("Retrieving filepath of song");
_logger.LogInformation("Retrieving filepath of song"); _logger.LogInformation("Retrieving filepath of song");
+9
View File
@@ -104,6 +104,15 @@ public class SongDataController : BaseController
if (up.SongData.Length > 0 && up.CoverArtData.Length > 0 && !string.IsNullOrEmpty(up.SongFile)) if (up.SongData.Length > 0 && up.CoverArtData.Length > 0 && !string.IsNullOrEmpty(up.SongFile))
{ {
var song = Newtonsoft.Json.JsonConvert.DeserializeObject<Song>(up.SongFile); var song = Newtonsoft.Json.JsonConvert.DeserializeObject<Song>(up.SongFile);
var tokMgr = new TokenManager(this._config);
var accessToken = Request.Headers["Authorization"];
var userId = tokMgr.RetrieveUserIdFromToken(accessToken);
if (userId != -1)
{
song.UserID = userId;
}
_logger.LogInformation($"Song title: {song.Title}"); _logger.LogInformation($"Song title: {song.Title}");
_songMgr.SaveSongToFileSystem(up.SongData, up.CoverArtData, song); _songMgr.SaveSongToFileSystem(up.SongData, up.CoverArtData, song);
+2 -19
View File
@@ -1,19 +1,6 @@
using System; using Microsoft.AspNetCore.Authorization;
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.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Icarus.Models;
using Icarus.Controllers.Managers;
using Icarus.Database.Contexts; using Icarus.Database.Contexts;
namespace Icarus.Controllers.V1; namespace Icarus.Controllers.V1;
@@ -25,7 +12,6 @@ public class SongStreamController : BaseController
{ {
#region Fields #region Fields
private ILogger<SongStreamController> _logger; private ILogger<SongStreamController> _logger;
private string _connectionString;
#endregion #endregion
@@ -38,7 +24,6 @@ public class SongStreamController : BaseController
{ {
_logger = logger; _logger = logger;
_config = config; _config = config;
_connectionString = _config.GetConnectionString("DefaultConnection");
} }
#endregion #endregion
@@ -63,11 +48,9 @@ public class SongStreamController : BaseController
_logger.LogInformation("Starting to stream song...>"); _logger.LogInformation("Starting to stream song...>");
Console.WriteLine("Starting to streamsong..."); Console.WriteLine("Starting to streamsong...");
var file = await Task.Run(() => { return await Task.Run(() => {
return File(stream, "application/octet-stream", filename); return File(stream, "application/octet-stream", filename);
}); });
return file;
} }
#endregion #endregion
} }
-4
View File
@@ -1,7 +1,3 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Icarus.Models; using Icarus.Models;
-4
View File
@@ -1,7 +1,3 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Icarus.Models; using Icarus.Models;
-4
View File
@@ -1,7 +1,3 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Icarus.Models; using Icarus.Models;
-4
View File
@@ -1,7 +1,3 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Icarus.Models; using Icarus.Models;
+6 -10
View File
@@ -1,7 +1,3 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Icarus.Models; using Icarus.Models;
@@ -27,19 +23,19 @@ public class SongContext : DbContext
modelBuilder.Entity<Song>() modelBuilder.Entity<Song>()
.Property(s => s.Year) .Property(s => s.Year)
.IsRequired(false); .IsRequired(false);
modelBuilder.Entity<Song>() modelBuilder.Entity<Song>()
.Property(s => s.GenreID) .Property(s => s.GenreID)
.IsRequired(false); .IsRequired(false);
modelBuilder.Entity<Song>() modelBuilder.Entity<Song>()
.Property(s => s.ArtistID) .Property(s => s.ArtistID)
.IsRequired(false); .IsRequired(false);
modelBuilder.Entity<Song>() modelBuilder.Entity<Song>()
.Property(s => s.AlbumID) .Property(s => s.AlbumID)
.IsRequired(false); .IsRequired(false);
modelBuilder.Entity<Song>() modelBuilder.Entity<Song>()
.Property(s => s.CoverArtID) .Property(s => s.CoverArtID)
.IsRequired(false); .IsRequired(false);
} }
-5
View File
@@ -1,9 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Icarus.Models; using Icarus.Models;
+2
View File
@@ -46,6 +46,8 @@ public class Song
public int? CoverArtID { get; set; } public int? CoverArtID { get; set; }
[JsonProperty("date_created")] [JsonProperty("date_created")]
public DateTime DateCreated { get; set; } public DateTime DateCreated { get; set; }
[JsonProperty("user_id")]
public int UserID { get; set; }
#endregion #endregion
+6 -3
View File
@@ -117,7 +117,7 @@ Prior to starting the API, the Migrations must be applied. There are 6 tables wi
* Song * Song
* Album * Album
* Artist * Artist
* Year * CoverArt
* 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/kdeng00/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:
@@ -126,12 +126,15 @@ scripts/Migrations/Linux/AddUpdate.sh
``` ```
Or you can manually add the migrations like so for each migration: Or you can manually add the migrations like so for each migration:
```shell ```shell
dotnet ef migrations Add [Migration] --context [Migration]Context dotnet-ef migrations Add InitialCreate --context UserContext
``` ```
Then update the migrations to the database like so<sup>*</sup>: Then update the migrations to the database like so<sup>*</sup>:
```shell ```shell
dotnet ef database update --context [Migration]Context dotnet-ef database update --context UserContext
``` ```
All of the contexts can be found in Database/Contexts folder.
From this point the database has been successfully configured. Metadata and song filesystem locations can be saved. 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. <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.
+14 -13
View File
@@ -1,26 +1,27 @@
echo "Adding migrations..." echo "Adding migrations..."
echo "Adding User migration" echo "Adding User migration"
dotnet ef migrations add User --context UserContext dotnet-ef migrations add User --context UserContext
echo "Adding Song migration"
dotnet ef migrations add Song --context SongContext
echo "Adding Album migration" echo "Adding Album migration"
dotnet ef migrations add Album --context AlbumContext dotnet-ef migrations add Album --context AlbumContext
echo "Adding Artist migration" echo "Adding Artist migration"
dotnet ef migrations add Artist --context ArtistContext dotnet-ef migrations add Artist --context ArtistContext
echo "Adding Genre migration" echo "Adding Genre migration"
dotnet ef migrations add Genre --context GenreContext dotnet-ef migrations add Genre --context GenreContext
echo "Adding Year migration"
dotnet ef migrations add Year --context YearContext
echo "Adding Cover art migration" echo "Adding Cover art migration"
dotnet ef migrations add CoverArt --context CoverArtContext dotnet-ef migrations add CoverArt --context CoverArtContext
echo "Adding Song migration"
dotnet-ef migrations add Song --context SongContext
echo "Updating migrations.." echo "Updating migrations.."
echo "Updating User migration" echo "Updating User migration"
dotnet ef database update --context UserContext dotnet-ef database update --context UserContext
echo "Updating Song migration"
echo "Updating Album migration" echo "Updating Album migration"
dotnet-ef database update --context AlbumContext
echo "Updating Artist migration" echo "Updating Artist migration"
dotnet-ef database update --context ArtistContext
echo "Updating Genre migration" echo "Updating Genre migration"
echo "Updating Year migration" dotnet-ef database update --context GenreContext
echo "Updating Cover art migration" echo "Updating Cover art migration"
dotnet ef database update --context SongContext dotnet-ef database update --context CoverArtContext
echo "Updating Song migration"
dotnet-ef database update --context SongContext