feat: add database connections, initial migration and early CLI tooling
All checks were successful
Update changelog / changelog (push) Successful in 25s
All checks were successful
Update changelog / changelog (push) Successful in 25s
This commit is contained in:
@@ -14,10 +14,11 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container: docker.io/thegeeklab/git-sv:2.0.9
|
container: docker.io/thegeeklab/git-sv:2.0.9
|
||||||
steps:
|
steps:
|
||||||
- name: install tools
|
- name: Install tools
|
||||||
run: |
|
run: |
|
||||||
apk add -q --update --no-cache nodejs curl jq sed
|
apk add -q --update --no-cache nodejs curl jq sed
|
||||||
- uses: actions/checkout@v6
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Generate upcoming changelog
|
- name: Generate upcoming changelog
|
||||||
|
|||||||
304
Controllers/Cli.cs
Normal file
304
Controllers/Cli.cs
Normal file
@@ -0,0 +1,304 @@
|
|||||||
|
using Shadow.Data;
|
||||||
|
using Shadow.Entities;
|
||||||
|
using System.Security;
|
||||||
|
|
||||||
|
namespace Shadow.Controllers;
|
||||||
|
public class Cli
|
||||||
|
{
|
||||||
|
private readonly ApplicationDbContext db;
|
||||||
|
private readonly GeneralUseHelpers guhf;
|
||||||
|
private readonly string[] args;
|
||||||
|
|
||||||
|
// TODO: Add "changeUser"
|
||||||
|
|
||||||
|
public Cli(ApplicationDbContext _db, GeneralUseHelpers _guhf, string[] _args)
|
||||||
|
{
|
||||||
|
db = _db;
|
||||||
|
guhf = _guhf;
|
||||||
|
args = _args;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> Parse()
|
||||||
|
{
|
||||||
|
// Returns true if the program can finish execution.
|
||||||
|
bool exit = true;
|
||||||
|
// Check if anything has been passed
|
||||||
|
if (args.Length == 0 || args.FirstOrDefault() is null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
switch (args[0].ToLower())
|
||||||
|
{
|
||||||
|
case "adduser":
|
||||||
|
AddUser();
|
||||||
|
break;
|
||||||
|
case "resetpassword":
|
||||||
|
ResetPassword();
|
||||||
|
break;
|
||||||
|
case "removeuser":
|
||||||
|
RemoveUser();
|
||||||
|
break;
|
||||||
|
case "help":
|
||||||
|
case "--help":
|
||||||
|
case "h":
|
||||||
|
case "-h":
|
||||||
|
case "/h":
|
||||||
|
case "/?":
|
||||||
|
case "?":
|
||||||
|
ShowHelp();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Console.WriteLine($"Unknown option: \"{args[0]}\". See \"help\" for available arguments.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ShowHelp()
|
||||||
|
{
|
||||||
|
// Shown when "Shadow --help"/"Shadow help"/... is ran.
|
||||||
|
Console.WriteLine(
|
||||||
|
$"--- Shadow commandline utility ---\n" +
|
||||||
|
$"Shadow version: #{ThisAssembly.Git.Commit} {ThisAssembly.Git.Branch} ({ThisAssembly.Git.CommitDate})\n\n" +
|
||||||
|
$"Available commands:\n" +
|
||||||
|
$"- addUser [username] - create a new user,\n" +
|
||||||
|
$"- resetPassword [username] - reset a user's password,\n" +
|
||||||
|
$"- removeUser [username] - remove the user COMPLETELY.\n\n" +
|
||||||
|
$"Username is optional. If not provided, user will be prompted to enter it.\n" +
|
||||||
|
$"Running without specifying a command launches a Kestrel web server.\n\n" +
|
||||||
|
$"License: AGPLv3+, Source code: https://gitea.7o7.cx/sherl/Shadow"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddUser()
|
||||||
|
{
|
||||||
|
// Check if any cli arguments have been passed
|
||||||
|
// Both:
|
||||||
|
// Shadow addUser
|
||||||
|
// and
|
||||||
|
// Shadow addUser username
|
||||||
|
// are supported.
|
||||||
|
|
||||||
|
Console.WriteLine($"[Shadow] Add a new user");
|
||||||
|
if (args.Length == 2)
|
||||||
|
Console.WriteLine($" You will be promped to enter a password.");
|
||||||
|
else
|
||||||
|
Console.WriteLine($" You will be promped to enter a username and password.");
|
||||||
|
|
||||||
|
string? username = null;
|
||||||
|
if (args.Length != 2)
|
||||||
|
{
|
||||||
|
while (username is null || username == String.Empty)
|
||||||
|
username = ReadName(" Please enter a username: ");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if user by this name exists in DB
|
||||||
|
User? foundUser = db.Users
|
||||||
|
.FirstOrDefault(u => u.NormalizedName == username!.ToLower());
|
||||||
|
if (foundUser != null) {
|
||||||
|
Console.WriteLine("Error! User with this name already exists in the database!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string password = ReadPassword();
|
||||||
|
string passwordConfirmation = ReadPassword(" Confirm password: ");
|
||||||
|
if (!password.Equals(passwordConfirmation))
|
||||||
|
{
|
||||||
|
Console.WriteLine("Error! Passwords do not match. Please try again.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
bool isUserAdmin = YesNoPrompt($" Should \"{username}\" be an administrator? [y/N]: ", false);
|
||||||
|
|
||||||
|
User newUser = new User
|
||||||
|
{
|
||||||
|
Name = username!,
|
||||||
|
NormalizedName = username!.ToLower(),
|
||||||
|
Password = password
|
||||||
|
};
|
||||||
|
|
||||||
|
db.Users.Add(newUser);
|
||||||
|
db.SaveChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ResetPassword()
|
||||||
|
{
|
||||||
|
// Check if any cli arguments have been passed
|
||||||
|
// Both:
|
||||||
|
// Shadow resetPassword
|
||||||
|
// and
|
||||||
|
// Shadow resetPassword username
|
||||||
|
// are supported.
|
||||||
|
|
||||||
|
Console.WriteLine($"[Shadow] Reset password");
|
||||||
|
if (args.Length == 2)
|
||||||
|
Console.WriteLine($" You will be promped to enter a new password.");
|
||||||
|
else
|
||||||
|
Console.WriteLine($" You will be promped to enter a username and a new password.");
|
||||||
|
|
||||||
|
string? username = null;
|
||||||
|
if (args.Length != 2)
|
||||||
|
{
|
||||||
|
while (username is null || username == String.Empty)
|
||||||
|
username = ReadName(" Please enter a username: ");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if user by this name exists in DB
|
||||||
|
User? foundUser = db.Users
|
||||||
|
.FirstOrDefault(u => u.NormalizedName == username!.ToLower());
|
||||||
|
if (foundUser == null)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Error! User with this name does not exist in the database!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string password = ReadPassword();
|
||||||
|
string passwordConfirmation = ReadPassword(" Confirm new password: ");
|
||||||
|
if (!password.Equals(passwordConfirmation))
|
||||||
|
{
|
||||||
|
Console.WriteLine("Error! Passwords do not match. Please try again.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foundUser.Password = password;
|
||||||
|
db.SaveChanges();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveUser()
|
||||||
|
{
|
||||||
|
// Check if any cli arguments have been passed
|
||||||
|
// Both:
|
||||||
|
// Shadow removeUser
|
||||||
|
// and
|
||||||
|
// Shadow removeUser username
|
||||||
|
// are supported.
|
||||||
|
|
||||||
|
Console.WriteLine($"[Shadow] Remove user");
|
||||||
|
if (args.Length == 2)
|
||||||
|
Console.WriteLine($" You will be promped to enter the password.");
|
||||||
|
else
|
||||||
|
Console.WriteLine($" You will be promped to enter the username and password.");
|
||||||
|
|
||||||
|
string? username = null;
|
||||||
|
if (args.Length != 2)
|
||||||
|
{
|
||||||
|
while (username is null || username == String.Empty)
|
||||||
|
username = ReadName(" Please enter the username: ");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if user by this name exists in DB
|
||||||
|
User? foundUser = db.Users
|
||||||
|
.FirstOrDefault(u => u.NormalizedName == username!.ToLower());
|
||||||
|
if (foundUser == null)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Error! User with this name does not exist in the database!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string password = ReadPassword();
|
||||||
|
string passwordConfirmation = ReadPassword(" Confirm password: ");
|
||||||
|
if (!password.Equals(passwordConfirmation))
|
||||||
|
{
|
||||||
|
Console.WriteLine("Error! Passwords do not match. Please try again.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (foundUser.Password != password)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Error! Entered password does not match that of \"{username}\"!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool userDeletionConfirmation = YesNoPrompt($" Do you want to remove \"{username}\" completely?\n" +
|
||||||
|
$" This cannot be undone! [y/N]: ", false);
|
||||||
|
if (userDeletionConfirmation) {
|
||||||
|
|
||||||
|
// All playlists, interactions should be deleted as well.
|
||||||
|
List<AlbumInteraction> albumInteractions = db.AlbumInteractions
|
||||||
|
.Where(ai => ai.User == foundUser)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
List<SongInteraction> songInteractions = db.SongInteractions
|
||||||
|
.Where(si => si.User == foundUser)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
foreach(AlbumInteraction ai in albumInteractions)
|
||||||
|
db.AlbumInteractions.Remove(ai);
|
||||||
|
|
||||||
|
foreach (SongInteraction si in songInteractions)
|
||||||
|
db.SongInteractions.Remove(si);
|
||||||
|
|
||||||
|
db.Users.Remove(foundUser);
|
||||||
|
db.SaveChanges();
|
||||||
|
|
||||||
|
Console.WriteLine($"User \"{username}\" and all their data deleted successfully.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
Console.WriteLine("User not removed.");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ReadPassword(string prompt = " Enter password (will not be echoed back): ")
|
||||||
|
{
|
||||||
|
// https://www.silicloud.com/blog/how-to-hide-content-in-the-console-using-c/
|
||||||
|
string password = String.Empty;
|
||||||
|
ConsoleKeyInfo key;
|
||||||
|
|
||||||
|
bool exit = false;
|
||||||
|
Console.Write(prompt);
|
||||||
|
while (!exit) {
|
||||||
|
key = Console.ReadKey(true);
|
||||||
|
|
||||||
|
// Exit on enter
|
||||||
|
if (key.Key == ConsoleKey.Enter)
|
||||||
|
exit = true;
|
||||||
|
// Clear last character on backspace
|
||||||
|
else if (key.Key == ConsoleKey.Backspace && password.Length > 0)
|
||||||
|
password = password.Substring(0, (password.Length - 1));
|
||||||
|
// Append any other character
|
||||||
|
else
|
||||||
|
password += key.KeyChar;
|
||||||
|
}
|
||||||
|
Console.WriteLine();
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string? ReadName(string prompt = " Enter username: ")
|
||||||
|
{
|
||||||
|
Console.Write(prompt);
|
||||||
|
string? input = Console.ReadLine();
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool YesNoPrompt(string prompt, bool? default_value = null)
|
||||||
|
{
|
||||||
|
// Checks if user input starts with "y", and if it does, returns true.
|
||||||
|
// Otherwise checks for "n". If both checks fail, and default_value is null,
|
||||||
|
// user will be asked repeatedly.
|
||||||
|
bool exit = false;
|
||||||
|
bool response = false;
|
||||||
|
|
||||||
|
Console.Write(prompt);
|
||||||
|
string? input = Console.ReadLine();
|
||||||
|
|
||||||
|
while (!exit)
|
||||||
|
{
|
||||||
|
if (input is not null && input.Length > 0)
|
||||||
|
if (input.ToLower().StartsWith("y"))
|
||||||
|
{
|
||||||
|
response = true;
|
||||||
|
exit = true;
|
||||||
|
}
|
||||||
|
else if (input.ToLower().StartsWith("n"))
|
||||||
|
{
|
||||||
|
response = false;
|
||||||
|
exit = true;
|
||||||
|
}
|
||||||
|
else if (default_value is not null)
|
||||||
|
{
|
||||||
|
response = (bool)default_value;
|
||||||
|
exit = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
||||||
30
Controllers/GeneralUseHelperFunctions.cs
Normal file
30
Controllers/GeneralUseHelperFunctions.cs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Shadow.Data;
|
||||||
|
using Shadow.Entities;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Shadow.Controllers;
|
||||||
|
|
||||||
|
public class GeneralUseHelpers(ApplicationDbContext db, IConfiguration appsettings)
|
||||||
|
{
|
||||||
|
private readonly ApplicationDbContext _db = db;
|
||||||
|
private readonly IConfiguration _appsettings = appsettings;
|
||||||
|
|
||||||
|
|
||||||
|
//async public Task<User?> GetUserFromEmail(string email)
|
||||||
|
//{
|
||||||
|
// return await _db.Users.FirstOrDefaultAsync(e => e.Email == email);
|
||||||
|
//}
|
||||||
|
|
||||||
|
//public string HashWithSHA512(string s)
|
||||||
|
//{
|
||||||
|
// using (var sha512 = SHA512.Create())
|
||||||
|
// {
|
||||||
|
// byte[] bytes = Encoding.ASCII.GetBytes(s);
|
||||||
|
// byte[] hash = sha512.ComputeHash(bytes);
|
||||||
|
// string hashstring = BitConverter.ToString(hash).Replace("-", "").ToLower();
|
||||||
|
// return hashstring;
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
}
|
||||||
29
Controllers/Seeder.cs
Normal file
29
Controllers/Seeder.cs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Shadow.Controllers;
|
||||||
|
using Shadow.Data;
|
||||||
|
using Shadow.Entities;
|
||||||
|
|
||||||
|
namespace Shadow.Controllers;
|
||||||
|
public class Seeder
|
||||||
|
{
|
||||||
|
private readonly ApplicationDbContext db;
|
||||||
|
private readonly GeneralUseHelpers guhf;
|
||||||
|
|
||||||
|
public Seeder(ApplicationDbContext _db, GeneralUseHelpers _guhf)
|
||||||
|
{
|
||||||
|
db = _db;
|
||||||
|
guhf = _guhf;
|
||||||
|
}
|
||||||
|
public void Seed()
|
||||||
|
{
|
||||||
|
|
||||||
|
// TODO: Ensure [unknown album], [unknown artist] exist
|
||||||
|
|
||||||
|
// TODO: Force add a new user through CLI if no users exist
|
||||||
|
|
||||||
|
|
||||||
|
Console.WriteLine($"You're running Shadow, commit {ThisAssembly.Git.Commit} of branch {ThisAssembly.Git.Branch} ({ThisAssembly.Git.CommitDate})\n");
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using SharpExifTool;
|
|
||||||
|
|
||||||
namespace Shadow.Controllers
|
|
||||||
{
|
|
||||||
[ApiController]
|
|
||||||
[Route("[controller]")]
|
|
||||||
public class WeatherForecastController : ControllerBase
|
|
||||||
{
|
|
||||||
private static readonly string[] Summaries =
|
|
||||||
[
|
|
||||||
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
|
|
||||||
];
|
|
||||||
|
|
||||||
[HttpGet(Name = "GetWeatherForecast")]
|
|
||||||
//public async Task<IEnumerable<WeatherForecast>> Get()
|
|
||||||
public async Task<WeatherForecast> Get()
|
|
||||||
{
|
|
||||||
using (var exiftool = new ExifTool())
|
|
||||||
{
|
|
||||||
//var test = await exiftool.ExtractAllMetadataAsync(filename: "C:\\Path\\to\\file.flac/.mp3/.m4a/.ogg");
|
|
||||||
return new WeatherForecast();
|
|
||||||
}
|
|
||||||
//return Enumerable.Range(1, 5).Select(index => new WeatherForecast
|
|
||||||
//{
|
|
||||||
// Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
|
|
||||||
// TemperatureC = Random.Shared.Next(-20, 55),
|
|
||||||
// Summary = Summaries[Random.Shared.Next(Summaries.Length)]
|
|
||||||
//})
|
|
||||||
//.ToArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
75
Data/ApplicationDbContext.cs
Normal file
75
Data/ApplicationDbContext.cs
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Shadow.Entities;
|
||||||
|
using System.Diagnostics.Metrics;
|
||||||
|
using System.Reflection.Emit;
|
||||||
|
|
||||||
|
namespace Shadow.Data;
|
||||||
|
public class ApplicationDbContext : DbContext
|
||||||
|
{
|
||||||
|
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// EFC ORM Setup
|
||||||
|
public DbSet<Album> Albums => Set<Album>();
|
||||||
|
public DbSet<AlbumInteraction> AlbumInteractions => Set<AlbumInteraction>();
|
||||||
|
public DbSet<Artist> Artists => Set<Artist>();
|
||||||
|
public DbSet<Genre> Genres => Set<Genre>();
|
||||||
|
public DbSet<GenreSong> GenreSongs => Set<GenreSong>();
|
||||||
|
public DbSet<Image> Images => Set<Image>();
|
||||||
|
public DbSet<Playlist> Playlists => Set<Playlist>();
|
||||||
|
public DbSet<PlaylistSong> PlaylistSongs => Set<PlaylistSong>();
|
||||||
|
public DbSet<PlaylistUser> PlaylistUsers => Set<PlaylistUser>();
|
||||||
|
public DbSet<Radio> Radios => Set<Radio>();
|
||||||
|
public DbSet<Song> Songs => Set<Song>();
|
||||||
|
public DbSet<SongInteraction> SongInteractions => Set<SongInteraction>();
|
||||||
|
public DbSet<User> Users => Set<User>();
|
||||||
|
|
||||||
|
protected override void OnModelCreating(ModelBuilder builder)
|
||||||
|
{
|
||||||
|
base.OnModelCreating(builder);
|
||||||
|
|
||||||
|
// Composite keys setup
|
||||||
|
builder.Entity<AlbumInteraction>()
|
||||||
|
.HasKey(ai => new { ai.AlbumId, ai.UserId });
|
||||||
|
builder.Entity<GenreSong>()
|
||||||
|
.HasKey(gs => new { gs.GenreId, gs.SongId });
|
||||||
|
builder.Entity<PlaylistSong>()
|
||||||
|
.HasKey(ps => new { ps.PlaylistId, ps.SongId });
|
||||||
|
builder.Entity<PlaylistUser>()
|
||||||
|
.HasKey(pu => new { pu.PlaylistId, pu.UserId });
|
||||||
|
builder.Entity<SongInteraction>()
|
||||||
|
.HasKey(si => new { si.SongId, si.UserId });
|
||||||
|
|
||||||
|
// Constraints (UQ)
|
||||||
|
builder.Entity<Album>(a => {
|
||||||
|
a.HasIndex(a => a.Uri).IsUnique();
|
||||||
|
});
|
||||||
|
builder.Entity<Artist>(a => {
|
||||||
|
a.HasIndex(a => a.NormalizedName).IsUnique();
|
||||||
|
});
|
||||||
|
builder.Entity<Genre>(g => {
|
||||||
|
g.HasIndex(g => g.NormalizedName).IsUnique();
|
||||||
|
});
|
||||||
|
builder.Entity<Playlist>(p => {
|
||||||
|
p.HasIndex(p => p.Uri).IsUnique();
|
||||||
|
});
|
||||||
|
builder.Entity<Radio>(r => {
|
||||||
|
r.HasIndex(r => r.NormalizedName).IsUnique();
|
||||||
|
});
|
||||||
|
builder.Entity<Song>(s => {
|
||||||
|
s.HasIndex(s => s.Uri).IsUnique();
|
||||||
|
});
|
||||||
|
builder.Entity<User>(u => {
|
||||||
|
u.HasIndex(u => u.Name).IsUnique();
|
||||||
|
// u.HasIndex(u => u.Email).IsUnique();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Force dependent side on Image
|
||||||
|
builder.Entity<Image>()
|
||||||
|
.HasOne(i => i.Song)
|
||||||
|
.WithOne(s => s.Image)
|
||||||
|
.HasForeignKey<Image>(i => i.SongId);
|
||||||
|
}
|
||||||
|
}
|
||||||
16
Entities/Album.cs
Normal file
16
Entities/Album.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
namespace Shadow.Entities;
|
||||||
|
public class Album
|
||||||
|
{
|
||||||
|
required public int Id { get; set; }
|
||||||
|
public required string Name { get; set; }
|
||||||
|
public required string Uri { get; set; }
|
||||||
|
public int State { get; set; } = 0;
|
||||||
|
public int? ArtistId { get; set; } = null;
|
||||||
|
|
||||||
|
public Artist? Artist { get; set; } = null;
|
||||||
|
public List<Song> Songs { get; set; } = [];
|
||||||
|
|
||||||
|
public bool IsOk() => State == 0;
|
||||||
|
public bool IsOrphaned() => State == 1;
|
||||||
|
public bool IsArchived() => State == 2;
|
||||||
|
}
|
||||||
12
Entities/AlbumInteraction.cs
Normal file
12
Entities/AlbumInteraction.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
namespace Shadow.Entities;
|
||||||
|
public class AlbumInteraction
|
||||||
|
{
|
||||||
|
required public int Id { get; set; }
|
||||||
|
required public int AlbumId { get; set; }
|
||||||
|
required public int UserId { get; set; }
|
||||||
|
public DateTime? PlayDate { get; set; } = null;
|
||||||
|
public bool Starred { get; set; } = false;
|
||||||
|
|
||||||
|
required public Album Album { get; set; }
|
||||||
|
required public User User { get; set; }
|
||||||
|
}
|
||||||
12
Entities/Artist.cs
Normal file
12
Entities/Artist.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
namespace Shadow.Entities;
|
||||||
|
public class Artist
|
||||||
|
{
|
||||||
|
required public int Id { get; set; }
|
||||||
|
required public string Name { get; set; }
|
||||||
|
required public string NormalizedName { get; set; }
|
||||||
|
|
||||||
|
// List of artists songs
|
||||||
|
public List<Song> Songs { get; set; } = [];
|
||||||
|
// List of artists albums
|
||||||
|
public List<Album> Albums { get; set; } = [];
|
||||||
|
}
|
||||||
10
Entities/Genre.cs
Normal file
10
Entities/Genre.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
namespace Shadow.Entities;
|
||||||
|
public class Genre
|
||||||
|
{
|
||||||
|
required public int Id { get; set; }
|
||||||
|
required public string Name { get; set; }
|
||||||
|
required public string NormalizedName { get; set; }
|
||||||
|
|
||||||
|
// List of genre-song pairs of a given genre
|
||||||
|
public List<GenreSong> GenreSongPair { get; set; } = [];
|
||||||
|
}
|
||||||
10
Entities/GenreSong.cs
Normal file
10
Entities/GenreSong.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
namespace Shadow.Entities;
|
||||||
|
public class GenreSong
|
||||||
|
{
|
||||||
|
// Composite keys
|
||||||
|
required public int GenreId { get; set; }
|
||||||
|
required public int SongId { get; set; }
|
||||||
|
|
||||||
|
required public Genre Genre { get; set; }
|
||||||
|
required public Song Song { get; set; }
|
||||||
|
}
|
||||||
14
Entities/Image.cs
Normal file
14
Entities/Image.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
namespace Shadow.Entities;
|
||||||
|
public class Image
|
||||||
|
{
|
||||||
|
required public int Id { get; set; }
|
||||||
|
required public string Uri { get; set; }
|
||||||
|
public int State { get; set; }
|
||||||
|
public int? SongId { get; set; } = null;
|
||||||
|
|
||||||
|
public Song? Song { get; set; } = null;
|
||||||
|
|
||||||
|
public bool IsAvailable() => State == 0;
|
||||||
|
public bool IsOrphaned() => State == 1;
|
||||||
|
|
||||||
|
}
|
||||||
25
Entities/Playlist.cs
Normal file
25
Entities/Playlist.cs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
namespace Shadow.Entities;
|
||||||
|
public class Playlist
|
||||||
|
{
|
||||||
|
required public int Id { get; set; }
|
||||||
|
required public string Name { get; set; }
|
||||||
|
required public string Uri { get; set; }
|
||||||
|
required public string Description { get; set; }
|
||||||
|
required public int CreatorId { get; set; } // UserId?
|
||||||
|
|
||||||
|
required public User Creator { get; set; }
|
||||||
|
public List<PlaylistUser> AuthorizedPlaylistUsers { get; set; } = [];
|
||||||
|
|
||||||
|
public bool CanAccess(User u) {
|
||||||
|
bool isUserPresent = false;
|
||||||
|
foreach (PlaylistUser pu in AuthorizedPlaylistUsers)
|
||||||
|
{
|
||||||
|
if (pu.User == u)
|
||||||
|
{
|
||||||
|
isUserPresent = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return isUserPresent;
|
||||||
|
}
|
||||||
|
}
|
||||||
12
Entities/PlaylistSong.cs
Normal file
12
Entities/PlaylistSong.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
namespace Shadow.Entities;
|
||||||
|
public class PlaylistSong
|
||||||
|
{
|
||||||
|
// Composite keys
|
||||||
|
required public int PlaylistId { get; set; }
|
||||||
|
required public int SongId { get; set; }
|
||||||
|
|
||||||
|
required public int Index { get; set; }
|
||||||
|
|
||||||
|
required public Playlist Playlist { get; set; }
|
||||||
|
required public Song Song { get; set; }
|
||||||
|
}
|
||||||
9
Entities/PlaylistUser.cs
Normal file
9
Entities/PlaylistUser.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
namespace Shadow.Entities;
|
||||||
|
public class PlaylistUser
|
||||||
|
{
|
||||||
|
required public int PlaylistId { get; set; }
|
||||||
|
required public int UserId { get; set; }
|
||||||
|
|
||||||
|
required public Playlist Playlist { get; set; }
|
||||||
|
required public User User { get; set; }
|
||||||
|
}
|
||||||
12
Entities/Radio.cs
Normal file
12
Entities/Radio.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
namespace Shadow.Entities;
|
||||||
|
public class Radio
|
||||||
|
{
|
||||||
|
required public int Id { get; set; }
|
||||||
|
required public string Name { get; set; }
|
||||||
|
required public string NormalizedName { get; set; }
|
||||||
|
public string? Homepage { get; set; } = null;
|
||||||
|
required public string Url { get; set; }
|
||||||
|
required public int UserId { get; set; }
|
||||||
|
|
||||||
|
required public User User { get; set; }
|
||||||
|
}
|
||||||
59
Entities/Song.cs
Normal file
59
Entities/Song.cs
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace Shadow.Entities;
|
||||||
|
public class Song
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
required public string Title { get; set; }
|
||||||
|
required public string Uri { get; set; }
|
||||||
|
required public string Filepath { get; set; }
|
||||||
|
public int State { get; set; }
|
||||||
|
required public string Filetype { get; set; }
|
||||||
|
public DateTime Date { get; set; }
|
||||||
|
public int Duration { get; set; }
|
||||||
|
public int Bitrate { get; set; }
|
||||||
|
public int Size { get; set; }
|
||||||
|
public string? Comment { get; set; } = null;
|
||||||
|
public int Channels { get; set; } = 2;
|
||||||
|
public int SamplingRate { get; set; }
|
||||||
|
public int? BitDepth { get; set; } = null;
|
||||||
|
public int Index { get; set; }
|
||||||
|
public int? TrackNumber { get; set; } = null;
|
||||||
|
public int? DiscNumber { get; set; } = null;
|
||||||
|
required public int AlbumId { get; set; }
|
||||||
|
required public int ArtistId { get; set; }
|
||||||
|
public int? ImageId { get; set; } = null;
|
||||||
|
|
||||||
|
// Songs without an album entry shall default to "[Unnamed album]".
|
||||||
|
required public Album Album { get; set; }
|
||||||
|
// Same for artists, with "[Unknown artist]".
|
||||||
|
required public Artist Artist { get; set; }
|
||||||
|
public List<GenreSong> GenreSongPair { get; set; } = [];
|
||||||
|
public Image? Image { get; set; } = null;
|
||||||
|
|
||||||
|
public bool IsOk() => State == 0;
|
||||||
|
public bool IsOrphaned() => State == 1;
|
||||||
|
public bool IsArchived() => State == 2;
|
||||||
|
public string GetMimeType()
|
||||||
|
{
|
||||||
|
// Might be nice to rewrite like this later:
|
||||||
|
// https://stackoverflow.com/a/47601452
|
||||||
|
string mimeType = String.Empty;
|
||||||
|
switch (Filetype)
|
||||||
|
{
|
||||||
|
case "flac":
|
||||||
|
mimeType = "audio/flac"; break;
|
||||||
|
case "m4a":
|
||||||
|
mimeType = "audio/mp4"; break;
|
||||||
|
case "mp3":
|
||||||
|
mimeType = "audio/mpeg"; break;
|
||||||
|
case "ogg":
|
||||||
|
mimeType = "audio/ogg"; break;
|
||||||
|
case "wav":
|
||||||
|
mimeType = "audio/x-wav"; break;
|
||||||
|
default:
|
||||||
|
mimeType = "audio/unknown"; break;
|
||||||
|
}
|
||||||
|
return mimeType;
|
||||||
|
}
|
||||||
|
}
|
||||||
15
Entities/SongInteraction.cs
Normal file
15
Entities/SongInteraction.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
namespace Shadow.Entities;
|
||||||
|
public class SongInteraction
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
required public int SongId { get; set; }
|
||||||
|
required public int UserId { get; set; }
|
||||||
|
DateTime? PlayDate { get; set; } = null;
|
||||||
|
public int PlayCount { get; set; } = 0;
|
||||||
|
public bool? Starred { get; set; } = false;
|
||||||
|
public int Rating { get; set; } = 0;
|
||||||
|
|
||||||
|
public Song? Song { get; set; }
|
||||||
|
public User? User { get; set; }
|
||||||
|
|
||||||
|
}
|
||||||
18
Entities/User.cs
Normal file
18
Entities/User.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
namespace Shadow.Entities;
|
||||||
|
public class User
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
required public string Name { get; set; }
|
||||||
|
required public string NormalizedName { get; set; }
|
||||||
|
// required public string Email { get; set; } // Currently not used
|
||||||
|
required public string Password { get; set; }
|
||||||
|
public int Role { get; set; } = 1;
|
||||||
|
|
||||||
|
public List<Playlist> Playlists { get; set; } = [];
|
||||||
|
public List<AlbumInteraction> AlbumInteractions { get; set; } = [];
|
||||||
|
public List<SongInteraction> SongInteractions { get; set; } = [];
|
||||||
|
|
||||||
|
public bool IsAdmin() => Role == 0;
|
||||||
|
public bool IsUnpriviledgedUser() => Role == 1;
|
||||||
|
|
||||||
|
}
|
||||||
608
Migrations/20251209000751_InitialMigration.Designer.cs
generated
Normal file
608
Migrations/20251209000751_InitialMigration.Designer.cs
generated
Normal file
@@ -0,0 +1,608 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
using Shadow.Data;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Shadow.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(ApplicationDbContext))]
|
||||||
|
[Migration("20251209000751_InitialMigration")]
|
||||||
|
partial class InitialMigration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "10.0.0")
|
||||||
|
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||||
|
|
||||||
|
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||||
|
|
||||||
|
modelBuilder.Entity("Shadow.Entities.Album", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<int?>("ArtistId")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<int>("State")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<string>("Uri")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("ArtistId");
|
||||||
|
|
||||||
|
b.HasIndex("Uri")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("Albums");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Shadow.Entities.AlbumInteraction", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("AlbumId")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int>("UserId")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("PlayDate")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<bool>("Starred")
|
||||||
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
|
b.HasKey("AlbumId", "UserId");
|
||||||
|
|
||||||
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
|
b.ToTable("AlbumInteractions");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Shadow.Entities.Artist", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("NormalizedName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("NormalizedName")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("Artists");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Shadow.Entities.Genre", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("NormalizedName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("NormalizedName")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("Genres");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Shadow.Entities.GenreSong", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("GenreId")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int>("SongId")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.HasKey("GenreId", "SongId");
|
||||||
|
|
||||||
|
b.HasIndex("SongId");
|
||||||
|
|
||||||
|
b.ToTable("GenreSongs");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Shadow.Entities.Image", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<int?>("SongId")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int>("State")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<string>("Uri")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("SongId")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("Images");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Shadow.Entities.Playlist", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<int>("CreatorId")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Uri")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("CreatorId");
|
||||||
|
|
||||||
|
b.HasIndex("Uri")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("Playlists");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Shadow.Entities.PlaylistSong", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("PlaylistId")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int>("SongId")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int>("Index")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.HasKey("PlaylistId", "SongId");
|
||||||
|
|
||||||
|
b.HasIndex("SongId");
|
||||||
|
|
||||||
|
b.ToTable("PlaylistSongs");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Shadow.Entities.PlaylistUser", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("PlaylistId")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int>("UserId")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.HasKey("PlaylistId", "UserId");
|
||||||
|
|
||||||
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
|
b.ToTable("PlaylistUsers");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Shadow.Entities.Radio", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("Homepage")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("NormalizedName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Url")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<int>("UserId")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("NormalizedName")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
|
b.ToTable("Radios");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Shadow.Entities.Song", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<int>("AlbumId")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int>("ArtistId")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int?>("BitDepth")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int>("Bitrate")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int>("Channels")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<string>("Comment")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Date")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<int?>("DiscNumber")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int>("Duration")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<string>("Filepath")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Filetype")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<int?>("ImageId")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int>("Index")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int>("SamplingRate")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int>("Size")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int>("State")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<string>("Title")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<int?>("TrackNumber")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<string>("Uri")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("AlbumId");
|
||||||
|
|
||||||
|
b.HasIndex("ArtistId");
|
||||||
|
|
||||||
|
b.HasIndex("Uri")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("Songs");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Shadow.Entities.SongInteraction", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("SongId")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int>("UserId")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int>("PlayCount")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int>("Rating")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<bool?>("Starred")
|
||||||
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
|
b.HasKey("SongId", "UserId");
|
||||||
|
|
||||||
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
|
b.ToTable("SongInteractions");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Shadow.Entities.User", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("NormalizedName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Password")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<int>("Role")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Name")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("Users");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Shadow.Entities.Album", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Shadow.Entities.Artist", "Artist")
|
||||||
|
.WithMany("Albums")
|
||||||
|
.HasForeignKey("ArtistId");
|
||||||
|
|
||||||
|
b.Navigation("Artist");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Shadow.Entities.AlbumInteraction", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Shadow.Entities.Album", "Album")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("AlbumId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("Shadow.Entities.User", "User")
|
||||||
|
.WithMany("AlbumInteractions")
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Album");
|
||||||
|
|
||||||
|
b.Navigation("User");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Shadow.Entities.GenreSong", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Shadow.Entities.Genre", "Genre")
|
||||||
|
.WithMany("GenreSongPair")
|
||||||
|
.HasForeignKey("GenreId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("Shadow.Entities.Song", "Song")
|
||||||
|
.WithMany("GenreSongPair")
|
||||||
|
.HasForeignKey("SongId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Genre");
|
||||||
|
|
||||||
|
b.Navigation("Song");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Shadow.Entities.Image", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Shadow.Entities.Song", "Song")
|
||||||
|
.WithOne("Image")
|
||||||
|
.HasForeignKey("Shadow.Entities.Image", "SongId");
|
||||||
|
|
||||||
|
b.Navigation("Song");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Shadow.Entities.Playlist", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Shadow.Entities.User", "Creator")
|
||||||
|
.WithMany("Playlists")
|
||||||
|
.HasForeignKey("CreatorId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Creator");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Shadow.Entities.PlaylistSong", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Shadow.Entities.Playlist", "Playlist")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("PlaylistId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("Shadow.Entities.Song", "Song")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("SongId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Playlist");
|
||||||
|
|
||||||
|
b.Navigation("Song");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Shadow.Entities.PlaylistUser", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Shadow.Entities.Playlist", "Playlist")
|
||||||
|
.WithMany("AuthorizedPlaylistUsers")
|
||||||
|
.HasForeignKey("PlaylistId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("Shadow.Entities.User", "User")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Playlist");
|
||||||
|
|
||||||
|
b.Navigation("User");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Shadow.Entities.Radio", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Shadow.Entities.User", "User")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("User");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Shadow.Entities.Song", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Shadow.Entities.Album", "Album")
|
||||||
|
.WithMany("Songs")
|
||||||
|
.HasForeignKey("AlbumId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("Shadow.Entities.Artist", "Artist")
|
||||||
|
.WithMany("Songs")
|
||||||
|
.HasForeignKey("ArtistId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Album");
|
||||||
|
|
||||||
|
b.Navigation("Artist");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Shadow.Entities.SongInteraction", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Shadow.Entities.Song", "Song")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("SongId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("Shadow.Entities.User", "User")
|
||||||
|
.WithMany("SongInteractions")
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Song");
|
||||||
|
|
||||||
|
b.Navigation("User");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Shadow.Entities.Album", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Songs");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Shadow.Entities.Artist", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Albums");
|
||||||
|
|
||||||
|
b.Navigation("Songs");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Shadow.Entities.Genre", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("GenreSongPair");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Shadow.Entities.Playlist", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("AuthorizedPlaylistUsers");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Shadow.Entities.Song", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("GenreSongPair");
|
||||||
|
|
||||||
|
b.Navigation("Image");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Shadow.Entities.User", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("AlbumInteractions");
|
||||||
|
|
||||||
|
b.Navigation("Playlists");
|
||||||
|
|
||||||
|
b.Navigation("SongInteractions");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
458
Migrations/20251209000751_InitialMigration.cs
Normal file
458
Migrations/20251209000751_InitialMigration.cs
Normal file
@@ -0,0 +1,458 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Shadow.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class InitialMigration : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Artists",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "integer", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
Name = table.Column<string>(type: "text", nullable: false),
|
||||||
|
NormalizedName = table.Column<string>(type: "text", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Artists", x => x.Id);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Genres",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "integer", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
Name = table.Column<string>(type: "text", nullable: false),
|
||||||
|
NormalizedName = table.Column<string>(type: "text", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Genres", x => x.Id);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Users",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "integer", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
Name = table.Column<string>(type: "text", nullable: false),
|
||||||
|
NormalizedName = table.Column<string>(type: "text", nullable: false),
|
||||||
|
Password = table.Column<string>(type: "text", nullable: false),
|
||||||
|
Role = table.Column<int>(type: "integer", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Users", x => x.Id);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Albums",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "integer", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
Name = table.Column<string>(type: "text", nullable: false),
|
||||||
|
Uri = table.Column<string>(type: "text", nullable: false),
|
||||||
|
State = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
ArtistId = table.Column<int>(type: "integer", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Albums", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_Albums_Artists_ArtistId",
|
||||||
|
column: x => x.ArtistId,
|
||||||
|
principalTable: "Artists",
|
||||||
|
principalColumn: "Id");
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Playlists",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "integer", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
Name = table.Column<string>(type: "text", nullable: false),
|
||||||
|
Uri = table.Column<string>(type: "text", nullable: false),
|
||||||
|
Description = table.Column<string>(type: "text", nullable: false),
|
||||||
|
CreatorId = table.Column<int>(type: "integer", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Playlists", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_Playlists_Users_CreatorId",
|
||||||
|
column: x => x.CreatorId,
|
||||||
|
principalTable: "Users",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Radios",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "integer", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
Name = table.Column<string>(type: "text", nullable: false),
|
||||||
|
NormalizedName = table.Column<string>(type: "text", nullable: false),
|
||||||
|
Homepage = table.Column<string>(type: "text", nullable: true),
|
||||||
|
Url = table.Column<string>(type: "text", nullable: false),
|
||||||
|
UserId = table.Column<int>(type: "integer", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Radios", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_Radios_Users_UserId",
|
||||||
|
column: x => x.UserId,
|
||||||
|
principalTable: "Users",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "AlbumInteractions",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
AlbumId = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
UserId = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
Id = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
PlayDate = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||||
|
Starred = table.Column<bool>(type: "boolean", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_AlbumInteractions", x => new { x.AlbumId, x.UserId });
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_AlbumInteractions_Albums_AlbumId",
|
||||||
|
column: x => x.AlbumId,
|
||||||
|
principalTable: "Albums",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_AlbumInteractions_Users_UserId",
|
||||||
|
column: x => x.UserId,
|
||||||
|
principalTable: "Users",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Songs",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "integer", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
Title = table.Column<string>(type: "text", nullable: false),
|
||||||
|
Uri = table.Column<string>(type: "text", nullable: false),
|
||||||
|
Filepath = table.Column<string>(type: "text", nullable: false),
|
||||||
|
State = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
Filetype = table.Column<string>(type: "text", nullable: false),
|
||||||
|
Date = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||||
|
Duration = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
Bitrate = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
Size = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
Comment = table.Column<string>(type: "text", nullable: true),
|
||||||
|
Channels = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
SamplingRate = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
BitDepth = table.Column<int>(type: "integer", nullable: true),
|
||||||
|
Index = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
TrackNumber = table.Column<int>(type: "integer", nullable: true),
|
||||||
|
DiscNumber = table.Column<int>(type: "integer", nullable: true),
|
||||||
|
AlbumId = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
ArtistId = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
ImageId = table.Column<int>(type: "integer", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Songs", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_Songs_Albums_AlbumId",
|
||||||
|
column: x => x.AlbumId,
|
||||||
|
principalTable: "Albums",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_Songs_Artists_ArtistId",
|
||||||
|
column: x => x.ArtistId,
|
||||||
|
principalTable: "Artists",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "PlaylistUsers",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
PlaylistId = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
UserId = table.Column<int>(type: "integer", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_PlaylistUsers", x => new { x.PlaylistId, x.UserId });
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_PlaylistUsers_Playlists_PlaylistId",
|
||||||
|
column: x => x.PlaylistId,
|
||||||
|
principalTable: "Playlists",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_PlaylistUsers_Users_UserId",
|
||||||
|
column: x => x.UserId,
|
||||||
|
principalTable: "Users",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "GenreSongs",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
GenreId = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
SongId = table.Column<int>(type: "integer", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_GenreSongs", x => new { x.GenreId, x.SongId });
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_GenreSongs_Genres_GenreId",
|
||||||
|
column: x => x.GenreId,
|
||||||
|
principalTable: "Genres",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_GenreSongs_Songs_SongId",
|
||||||
|
column: x => x.SongId,
|
||||||
|
principalTable: "Songs",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Images",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "integer", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
Uri = table.Column<string>(type: "text", nullable: false),
|
||||||
|
State = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
SongId = table.Column<int>(type: "integer", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Images", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_Images_Songs_SongId",
|
||||||
|
column: x => x.SongId,
|
||||||
|
principalTable: "Songs",
|
||||||
|
principalColumn: "Id");
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "PlaylistSongs",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
PlaylistId = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
SongId = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
Index = table.Column<int>(type: "integer", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_PlaylistSongs", x => new { x.PlaylistId, x.SongId });
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_PlaylistSongs_Playlists_PlaylistId",
|
||||||
|
column: x => x.PlaylistId,
|
||||||
|
principalTable: "Playlists",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_PlaylistSongs_Songs_SongId",
|
||||||
|
column: x => x.SongId,
|
||||||
|
principalTable: "Songs",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "SongInteractions",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
SongId = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
UserId = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
Id = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
PlayCount = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
Starred = table.Column<bool>(type: "boolean", nullable: true),
|
||||||
|
Rating = table.Column<int>(type: "integer", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_SongInteractions", x => new { x.SongId, x.UserId });
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_SongInteractions_Songs_SongId",
|
||||||
|
column: x => x.SongId,
|
||||||
|
principalTable: "Songs",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_SongInteractions_Users_UserId",
|
||||||
|
column: x => x.UserId,
|
||||||
|
principalTable: "Users",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_AlbumInteractions_UserId",
|
||||||
|
table: "AlbumInteractions",
|
||||||
|
column: "UserId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Albums_ArtistId",
|
||||||
|
table: "Albums",
|
||||||
|
column: "ArtistId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Albums_Uri",
|
||||||
|
table: "Albums",
|
||||||
|
column: "Uri",
|
||||||
|
unique: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Artists_NormalizedName",
|
||||||
|
table: "Artists",
|
||||||
|
column: "NormalizedName",
|
||||||
|
unique: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Genres_NormalizedName",
|
||||||
|
table: "Genres",
|
||||||
|
column: "NormalizedName",
|
||||||
|
unique: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_GenreSongs_SongId",
|
||||||
|
table: "GenreSongs",
|
||||||
|
column: "SongId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Images_SongId",
|
||||||
|
table: "Images",
|
||||||
|
column: "SongId",
|
||||||
|
unique: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Playlists_CreatorId",
|
||||||
|
table: "Playlists",
|
||||||
|
column: "CreatorId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Playlists_Uri",
|
||||||
|
table: "Playlists",
|
||||||
|
column: "Uri",
|
||||||
|
unique: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_PlaylistSongs_SongId",
|
||||||
|
table: "PlaylistSongs",
|
||||||
|
column: "SongId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_PlaylistUsers_UserId",
|
||||||
|
table: "PlaylistUsers",
|
||||||
|
column: "UserId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Radios_NormalizedName",
|
||||||
|
table: "Radios",
|
||||||
|
column: "NormalizedName",
|
||||||
|
unique: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Radios_UserId",
|
||||||
|
table: "Radios",
|
||||||
|
column: "UserId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_SongInteractions_UserId",
|
||||||
|
table: "SongInteractions",
|
||||||
|
column: "UserId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Songs_AlbumId",
|
||||||
|
table: "Songs",
|
||||||
|
column: "AlbumId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Songs_ArtistId",
|
||||||
|
table: "Songs",
|
||||||
|
column: "ArtistId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Songs_Uri",
|
||||||
|
table: "Songs",
|
||||||
|
column: "Uri",
|
||||||
|
unique: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Users_Name",
|
||||||
|
table: "Users",
|
||||||
|
column: "Name",
|
||||||
|
unique: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "AlbumInteractions");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "GenreSongs");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Images");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "PlaylistSongs");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "PlaylistUsers");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Radios");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "SongInteractions");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Genres");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Playlists");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Songs");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Users");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Albums");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Artists");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
605
Migrations/ApplicationDbContextModelSnapshot.cs
Normal file
605
Migrations/ApplicationDbContextModelSnapshot.cs
Normal file
@@ -0,0 +1,605 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
using Shadow.Data;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Shadow.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(ApplicationDbContext))]
|
||||||
|
partial class ApplicationDbContextModelSnapshot : ModelSnapshot
|
||||||
|
{
|
||||||
|
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "10.0.0")
|
||||||
|
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||||
|
|
||||||
|
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||||
|
|
||||||
|
modelBuilder.Entity("Shadow.Entities.Album", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<int?>("ArtistId")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<int>("State")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<string>("Uri")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("ArtistId");
|
||||||
|
|
||||||
|
b.HasIndex("Uri")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("Albums");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Shadow.Entities.AlbumInteraction", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("AlbumId")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int>("UserId")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("PlayDate")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<bool>("Starred")
|
||||||
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
|
b.HasKey("AlbumId", "UserId");
|
||||||
|
|
||||||
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
|
b.ToTable("AlbumInteractions");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Shadow.Entities.Artist", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("NormalizedName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("NormalizedName")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("Artists");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Shadow.Entities.Genre", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("NormalizedName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("NormalizedName")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("Genres");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Shadow.Entities.GenreSong", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("GenreId")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int>("SongId")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.HasKey("GenreId", "SongId");
|
||||||
|
|
||||||
|
b.HasIndex("SongId");
|
||||||
|
|
||||||
|
b.ToTable("GenreSongs");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Shadow.Entities.Image", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<int?>("SongId")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int>("State")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<string>("Uri")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("SongId")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("Images");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Shadow.Entities.Playlist", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<int>("CreatorId")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Uri")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("CreatorId");
|
||||||
|
|
||||||
|
b.HasIndex("Uri")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("Playlists");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Shadow.Entities.PlaylistSong", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("PlaylistId")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int>("SongId")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int>("Index")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.HasKey("PlaylistId", "SongId");
|
||||||
|
|
||||||
|
b.HasIndex("SongId");
|
||||||
|
|
||||||
|
b.ToTable("PlaylistSongs");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Shadow.Entities.PlaylistUser", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("PlaylistId")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int>("UserId")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.HasKey("PlaylistId", "UserId");
|
||||||
|
|
||||||
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
|
b.ToTable("PlaylistUsers");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Shadow.Entities.Radio", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("Homepage")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("NormalizedName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Url")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<int>("UserId")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("NormalizedName")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
|
b.ToTable("Radios");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Shadow.Entities.Song", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<int>("AlbumId")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int>("ArtistId")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int?>("BitDepth")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int>("Bitrate")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int>("Channels")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<string>("Comment")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Date")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<int?>("DiscNumber")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int>("Duration")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<string>("Filepath")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Filetype")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<int?>("ImageId")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int>("Index")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int>("SamplingRate")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int>("Size")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int>("State")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<string>("Title")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<int?>("TrackNumber")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<string>("Uri")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("AlbumId");
|
||||||
|
|
||||||
|
b.HasIndex("ArtistId");
|
||||||
|
|
||||||
|
b.HasIndex("Uri")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("Songs");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Shadow.Entities.SongInteraction", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("SongId")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int>("UserId")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int>("PlayCount")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int>("Rating")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<bool?>("Starred")
|
||||||
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
|
b.HasKey("SongId", "UserId");
|
||||||
|
|
||||||
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
|
b.ToTable("SongInteractions");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Shadow.Entities.User", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("NormalizedName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Password")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<int>("Role")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Name")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("Users");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Shadow.Entities.Album", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Shadow.Entities.Artist", "Artist")
|
||||||
|
.WithMany("Albums")
|
||||||
|
.HasForeignKey("ArtistId");
|
||||||
|
|
||||||
|
b.Navigation("Artist");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Shadow.Entities.AlbumInteraction", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Shadow.Entities.Album", "Album")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("AlbumId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("Shadow.Entities.User", "User")
|
||||||
|
.WithMany("AlbumInteractions")
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Album");
|
||||||
|
|
||||||
|
b.Navigation("User");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Shadow.Entities.GenreSong", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Shadow.Entities.Genre", "Genre")
|
||||||
|
.WithMany("GenreSongPair")
|
||||||
|
.HasForeignKey("GenreId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("Shadow.Entities.Song", "Song")
|
||||||
|
.WithMany("GenreSongPair")
|
||||||
|
.HasForeignKey("SongId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Genre");
|
||||||
|
|
||||||
|
b.Navigation("Song");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Shadow.Entities.Image", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Shadow.Entities.Song", "Song")
|
||||||
|
.WithOne("Image")
|
||||||
|
.HasForeignKey("Shadow.Entities.Image", "SongId");
|
||||||
|
|
||||||
|
b.Navigation("Song");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Shadow.Entities.Playlist", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Shadow.Entities.User", "Creator")
|
||||||
|
.WithMany("Playlists")
|
||||||
|
.HasForeignKey("CreatorId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Creator");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Shadow.Entities.PlaylistSong", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Shadow.Entities.Playlist", "Playlist")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("PlaylistId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("Shadow.Entities.Song", "Song")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("SongId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Playlist");
|
||||||
|
|
||||||
|
b.Navigation("Song");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Shadow.Entities.PlaylistUser", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Shadow.Entities.Playlist", "Playlist")
|
||||||
|
.WithMany("AuthorizedPlaylistUsers")
|
||||||
|
.HasForeignKey("PlaylistId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("Shadow.Entities.User", "User")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Playlist");
|
||||||
|
|
||||||
|
b.Navigation("User");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Shadow.Entities.Radio", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Shadow.Entities.User", "User")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("User");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Shadow.Entities.Song", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Shadow.Entities.Album", "Album")
|
||||||
|
.WithMany("Songs")
|
||||||
|
.HasForeignKey("AlbumId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("Shadow.Entities.Artist", "Artist")
|
||||||
|
.WithMany("Songs")
|
||||||
|
.HasForeignKey("ArtistId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Album");
|
||||||
|
|
||||||
|
b.Navigation("Artist");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Shadow.Entities.SongInteraction", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Shadow.Entities.Song", "Song")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("SongId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("Shadow.Entities.User", "User")
|
||||||
|
.WithMany("SongInteractions")
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Song");
|
||||||
|
|
||||||
|
b.Navigation("User");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Shadow.Entities.Album", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Songs");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Shadow.Entities.Artist", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Albums");
|
||||||
|
|
||||||
|
b.Navigation("Songs");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Shadow.Entities.Genre", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("GenreSongPair");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Shadow.Entities.Playlist", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("AuthorizedPlaylistUsers");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Shadow.Entities.Song", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("GenreSongPair");
|
||||||
|
|
||||||
|
b.Navigation("Image");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Shadow.Entities.User", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("AlbumInteractions");
|
||||||
|
|
||||||
|
b.Navigation("Playlists");
|
||||||
|
|
||||||
|
b.Navigation("SongInteractions");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
49
Program.cs
49
Program.cs
@@ -1,12 +1,34 @@
|
|||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Mvc.Formatters;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.OpenApi;
|
using Microsoft.OpenApi;
|
||||||
|
using Shadow.Controllers;
|
||||||
|
using Shadow.Data;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
builder.Services.AddControllers();
|
string connectionString = builder.Configuration.GetConnectionString("DefaultConnection")
|
||||||
|
?? throw new ArgumentException("Connection string 'DefaultConnection' not found.");
|
||||||
|
builder.Services.AddDbContext<ApplicationDbContext>(options =>
|
||||||
|
{
|
||||||
|
options.UseNpgsql(connectionString);
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.Services.AddControllers(options =>
|
||||||
|
{
|
||||||
|
// Add XML serializer
|
||||||
|
options.OutputFormatters.Add(new XmlSerializerOutputFormatter());
|
||||||
|
}).AddJsonOptions(options =>
|
||||||
|
{
|
||||||
|
// Pretty-print JSON
|
||||||
|
options.JsonSerializerOptions.WriteIndented = true;
|
||||||
|
|
||||||
|
// Preserve keys' case
|
||||||
|
options.JsonSerializerOptions.PropertyNamingPolicy = null;
|
||||||
|
});
|
||||||
builder.Services.AddOpenApi();
|
builder.Services.AddOpenApi();
|
||||||
builder.Services.AddEndpointsApiExplorer();
|
builder.Services.AddEndpointsApiExplorer();
|
||||||
|
builder.Services.AddScoped<GeneralUseHelpers>();
|
||||||
builder.Services.AddSwaggerGen(options =>
|
builder.Services.AddSwaggerGen(options =>
|
||||||
{
|
{
|
||||||
options.SwaggerDoc("v1", new OpenApiInfo
|
options.SwaggerDoc("v1", new OpenApiInfo
|
||||||
@@ -41,12 +63,33 @@ builder.Services.AddSwaggerGen(options =>
|
|||||||
// [new OpenApiSecuritySchemeReference("bearer", document)] = []
|
// [new OpenApiSecuritySchemeReference("bearer", document)] = []
|
||||||
//});
|
//});
|
||||||
|
|
||||||
// using System.Reflection;
|
|
||||||
var xmlFilename = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
|
var xmlFilename = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
|
||||||
options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, xmlFilename));
|
options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, xmlFilename));
|
||||||
});
|
});
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
if (args.FirstOrDefault() is not null)
|
||||||
|
{
|
||||||
|
// Handle CLI if arguments have been passed.
|
||||||
|
bool shutdown = false;
|
||||||
|
using (IServiceScope scope = app.Services.CreateScope())
|
||||||
|
{
|
||||||
|
ApplicationDbContext db = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||||
|
GeneralUseHelpers guhf = scope.ServiceProvider.GetRequiredService<GeneralUseHelpers>();
|
||||||
|
Cli cli = new(db, guhf, args);
|
||||||
|
shutdown = await cli.Parse();
|
||||||
|
}
|
||||||
|
if (shutdown) return;
|
||||||
|
}
|
||||||
|
// Seed database
|
||||||
|
using (IServiceScope scope = app.Services.CreateScope())
|
||||||
|
{
|
||||||
|
ApplicationDbContext db = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||||
|
GeneralUseHelpers guhf = scope.ServiceProvider.GetRequiredService<GeneralUseHelpers>();
|
||||||
|
Seeder seeder = new(db, guhf);
|
||||||
|
seeder.Seed();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Configure the HTTP request pipeline.
|
// Configure the HTTP request pipeline.
|
||||||
if (app.Environment.IsDevelopment())
|
if (app.Environment.IsDevelopment())
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
},
|
},
|
||||||
"https": {
|
"https": {
|
||||||
"commandName": "Project",
|
"commandName": "Project",
|
||||||
|
"commandLineArgs": "",
|
||||||
"environmentVariables": {
|
"environmentVariables": {
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -19,10 +19,19 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="GitInfo" Version="3.6.0">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.0" />
|
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.0" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.0" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.0">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.23.0" />
|
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.23.0" />
|
||||||
<PackageReference Include="Npgsql" Version="10.0.0" />
|
<PackageReference Include="Npgsql" Version="10.0.0" />
|
||||||
|
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.0" />
|
||||||
<PackageReference Include="SharpExifTool" Version="13.32.0.1" />
|
<PackageReference Include="SharpExifTool" Version="13.32.0.1" />
|
||||||
<PackageReference Include="SkiaSharp" Version="3.119.1" />
|
<PackageReference Include="SkiaSharp" Version="3.119.1" />
|
||||||
<PackageReference Include="SkiaSharp.NativeAssets.Linux.NoDependencies" Version="3.119.1" />
|
<PackageReference Include="SkiaSharp.NativeAssets.Linux.NoDependencies" Version="3.119.1" />
|
||||||
@@ -32,4 +41,10 @@
|
|||||||
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="10.0.1" />
|
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="10.0.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="Migrations\" />
|
||||||
|
<Folder Include="Mapping\" />
|
||||||
|
<Folder Include="DTOs\" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
namespace Shadow
|
|
||||||
{
|
|
||||||
public class WeatherForecast
|
|
||||||
{
|
|
||||||
public DateOnly Date { get; set; }
|
|
||||||
|
|
||||||
public int TemperatureC { get; set; }
|
|
||||||
|
|
||||||
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
|
|
||||||
|
|
||||||
public string? Summary { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user