#145 : Ajout du seeder Spotify.
This commit is contained in:
34
Webzine.Business/DTOs/Spotify/SpotifyAlbumDto.cs
Normal file
34
Webzine.Business/DTOs/Spotify/SpotifyAlbumDto.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
namespace Webzine.Business.DTOs.Spotify
|
||||
{
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
/// <summary>
|
||||
/// Objet album de spotify.
|
||||
/// </summary>
|
||||
public class SpotifyAlbumDto
|
||||
{
|
||||
/// <summary>
|
||||
/// Id de l'album.
|
||||
/// </summary>
|
||||
[JsonPropertyName("id")]
|
||||
public string Id { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Nom de l'album.
|
||||
/// </summary>
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Date de sortie.
|
||||
/// </summary>
|
||||
[JsonPropertyName("release_date")]
|
||||
public string? ReleaseDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Urls de la jaquette.
|
||||
/// </summary>
|
||||
[JsonPropertyName("images")]
|
||||
public List<SpotifyImageDto> Images { get; set; } = new ();
|
||||
}
|
||||
}
|
||||
16
Webzine.Business/DTOs/Spotify/SpotifyAlbumsResponseDto.cs
Normal file
16
Webzine.Business/DTOs/Spotify/SpotifyAlbumsResponseDto.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
namespace Webzine.Business.DTOs.Spotify
|
||||
{
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
/// <summary>
|
||||
/// Objet Spotify qui contient une liste d'album.
|
||||
/// </summary>
|
||||
public class SpotifyAlbumsResponseDto
|
||||
{
|
||||
/// <summary>
|
||||
/// Container de plusieurs albums spotify.
|
||||
/// </summary>
|
||||
[JsonPropertyName("items")]
|
||||
public List<SpotifyAlbumDto> Items { get; set; } = new ();
|
||||
}
|
||||
}
|
||||
28
Webzine.Business/DTOs/Spotify/SpotifyArtistDto.cs
Normal file
28
Webzine.Business/DTOs/Spotify/SpotifyArtistDto.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
namespace Webzine.Business.DTOs.Spotify
|
||||
{
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
/// <summary>
|
||||
/// Objet artiste retourne depuis spotify.
|
||||
/// </summary>
|
||||
public class SpotifyArtistDto
|
||||
{
|
||||
/// <summary>
|
||||
/// Id de l'artiste.
|
||||
/// </summary>
|
||||
[JsonPropertyName("id")]
|
||||
public string Id { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Nom de l'artiste.
|
||||
/// </summary>
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Genre musical de l'artiste.
|
||||
/// </summary>
|
||||
[JsonPropertyName("genres")]
|
||||
public List<string> Genres { get; set; } = new ();
|
||||
}
|
||||
}
|
||||
16
Webzine.Business/DTOs/Spotify/SpotifyArtistsContainerDto.cs
Normal file
16
Webzine.Business/DTOs/Spotify/SpotifyArtistsContainerDto.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
namespace Webzine.Business.DTOs.Spotify
|
||||
{
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
/// <summary>
|
||||
/// Container d'artistes spotify.
|
||||
/// </summary>
|
||||
public class SpotifyArtistsContainerDto
|
||||
{
|
||||
/// <summary>
|
||||
/// Liste d'artiste spotify.
|
||||
/// </summary>
|
||||
[JsonPropertyName("items")]
|
||||
public List<SpotifyArtistDto> Items { get; set; } = new ();
|
||||
}
|
||||
}
|
||||
9
Webzine.Business/DTOs/Spotify/SpotifyExternalUrlsDto.cs
Normal file
9
Webzine.Business/DTOs/Spotify/SpotifyExternalUrlsDto.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace Webzine.Business.DTOs.Spotify;
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
public class SpotifyExternalUrlsDto
|
||||
{
|
||||
[JsonPropertyName("spotify")]
|
||||
public string? Spotify { get; set; }
|
||||
}
|
||||
9
Webzine.Business/DTOs/Spotify/SpotifyImageDto.cs
Normal file
9
Webzine.Business/DTOs/Spotify/SpotifyImageDto.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace Webzine.Business.DTOs.Spotify;
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
public class SpotifyImageDto
|
||||
{
|
||||
[JsonPropertyName("url")]
|
||||
public string? Url { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
namespace Webzine.Business.DTOs.Spotify
|
||||
{
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
/// <summary>
|
||||
/// Resultat d'une recherche spotify avec un objet artist.
|
||||
/// </summary>
|
||||
public class SpotifySearchArtistsResponseDto
|
||||
{
|
||||
/// <summary>
|
||||
/// Liste d'artistes.
|
||||
/// </summary>
|
||||
[JsonPropertyName("artists")]
|
||||
public SpotifyArtistsContainerDto? Artists { get; set; }
|
||||
}
|
||||
}
|
||||
16
Webzine.Business/DTOs/Spotify/SpotifyTokenResponseDto.cs
Normal file
16
Webzine.Business/DTOs/Spotify/SpotifyTokenResponseDto.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
namespace Webzine.Business.DTOs.Spotify
|
||||
{
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
/// <summary>
|
||||
/// Recuperation Token Bearer de spotify.
|
||||
/// </summary>
|
||||
public class SpotifyTokenResponseDto
|
||||
{
|
||||
/// <summary>
|
||||
/// Jeton d'acces.
|
||||
/// </summary>
|
||||
[JsonPropertyName("access_token")]
|
||||
public string? AccessToken { get; set; }
|
||||
}
|
||||
}
|
||||
34
Webzine.Business/DTOs/Spotify/SpotifyTrackDto.cs
Normal file
34
Webzine.Business/DTOs/Spotify/SpotifyTrackDto.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
namespace Webzine.Business.DTOs.Spotify
|
||||
{
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
/// <summary>
|
||||
/// Objet track de spotify.
|
||||
/// </summary>
|
||||
public class SpotifyTrackDto
|
||||
{
|
||||
/// <summary>
|
||||
/// Id de la track.
|
||||
/// </summary>
|
||||
[JsonPropertyName("id")]
|
||||
public string Id { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Nom de la musique.
|
||||
/// </summary>
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Duree de la musique en millieseconde.
|
||||
/// </summary>
|
||||
[JsonPropertyName("duration_ms")]
|
||||
public int DurationMs { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// urls spotify.
|
||||
/// </summary>
|
||||
[JsonPropertyName("external_urls")]
|
||||
public SpotifyExternalUrlsDto? ExternalUrls { get; set; }
|
||||
}
|
||||
}
|
||||
16
Webzine.Business/DTOs/Spotify/SpotifyTracksResponseDto.cs
Normal file
16
Webzine.Business/DTOs/Spotify/SpotifyTracksResponseDto.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
namespace Webzine.Business.DTOs.Spotify
|
||||
{
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
/// <summary>
|
||||
/// Reponse spotify qui contient une liste de tracks.
|
||||
/// </summary>
|
||||
public class SpotifyTracksResponseDto
|
||||
{
|
||||
/// <summary>
|
||||
/// Container qui contient plusieurs tracks.
|
||||
/// </summary>
|
||||
[JsonPropertyName("items")]
|
||||
public List<SpotifyTrackDto> Items { get; set; } = new ();
|
||||
}
|
||||
}
|
||||
128
Webzine.Business/Mappers/Spotify/SpotifyMapper.cs
Normal file
128
Webzine.Business/Mappers/Spotify/SpotifyMapper.cs
Normal file
@@ -0,0 +1,128 @@
|
||||
namespace Webzine.Business.Mappers.Spotify
|
||||
{
|
||||
using System.Globalization;
|
||||
|
||||
using Webzine.Business.DTOs.Spotify;
|
||||
using Webzine.Entity;
|
||||
|
||||
/// <summary>
|
||||
/// Mapper pour transformer les objets de Spotify (DTOs) en Entity.
|
||||
/// </summary>
|
||||
public static class SpotifyMapper
|
||||
{
|
||||
/// <summary>
|
||||
/// Permet d'ajouter ou de creer un style.
|
||||
/// </summary>
|
||||
/// <param name="styles">Dictionnaire string, Style.</param>
|
||||
/// <param name="genre">Genre.</param>
|
||||
/// <param name="nextStyleId">Id du style.</param>
|
||||
/// <returns>Le style.</returns>
|
||||
public static Style GetOrCreateStyle(Dictionary<string, Style> styles, string genre, ref int nextStyleId)
|
||||
{
|
||||
// On verifie si le genre est présent dans la liste de styles.
|
||||
if (!styles.TryGetValue(genre, out var style))
|
||||
{
|
||||
// Creation d'un nouveau style.
|
||||
style = new Style
|
||||
{
|
||||
IdStyle = nextStyleId++,
|
||||
Libelle = genre,
|
||||
Titres = new List<Titre>(),
|
||||
};
|
||||
|
||||
// Ajout dans la liste.
|
||||
styles.Add(style.Libelle, style);
|
||||
}
|
||||
|
||||
return style;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creation d'un nouvel artiste a l'aide des infos Spotify.
|
||||
/// </summary>
|
||||
/// <param name="artisteSpotify">Artiste spotify.</param>
|
||||
/// <param name="stylesTitre">Style spotify.</param>
|
||||
/// <param name="idArtiste">Id de l'artiste.</param>
|
||||
/// <returns>Artiste.</returns>
|
||||
public static Artiste ToArtiste(SpotifyArtistDto artisteSpotify, List<Style> stylesTitre, int idArtiste)
|
||||
{
|
||||
// Spotify ne possède pas de Biographie pour les artistes.
|
||||
// On affiche donc son nom, et les styles qui lui sont associés.
|
||||
return new Artiste
|
||||
{
|
||||
IdArtiste = idArtiste,
|
||||
Nom = artisteSpotify.Name,
|
||||
Biographie = $"{artisteSpotify.Name} est un artiste present sur Spotify, associe aux styles {string.Join(", ", stylesTitre.Select(s => s.Libelle))}.",
|
||||
Titres = new List<Titre>(),
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Permet de creer un titre depuis les donn<6E>es de Spotify.
|
||||
/// </summary>
|
||||
/// <param name="track">Titre de spotify.</param>
|
||||
/// <param name="album">Album de spotify.</param>
|
||||
/// <param name="artiste">Artiste mappe.</param>
|
||||
/// <param name="stylesTitre">Style du titre.</param>
|
||||
/// <param name="idTitre">Id du titre.</param>
|
||||
/// <returns>Nouveau Titre.</returns>
|
||||
public static Titre ToTitre(SpotifyTrackDto track, SpotifyAlbumDto album, Artiste artiste, List<Style> stylesTitre, int idTitre)
|
||||
{
|
||||
// Spotify ne fournit pas les elements suivants : Chronique, DateCreation, NbLectures, NbLikes.
|
||||
return new Titre
|
||||
{
|
||||
IdTitre = idTitre,
|
||||
IdArtiste = artiste.IdArtiste,
|
||||
Artiste = artiste,
|
||||
Libelle = track.Name,
|
||||
Chronique = $"{track.Name} est un titre de {artiste.Nom}, issu de l'album {album.Name}. Cette fiche a ete generee depuis Spotify.",
|
||||
DateCreation = DateTime.UtcNow,
|
||||
DateSortie = ParseDate(album.ReleaseDate),
|
||||
Duree = Math.Max(1, track.DurationMs / 1000),
|
||||
UrlJaquette = album.Images.FirstOrDefault()?.Url,
|
||||
UrlEcoute = Trim(track.ExternalUrls?.Spotify ?? $"https://open.spotify.com/track/{track.Id}", 250, string.Empty),
|
||||
NbLectures = Random.Shared.Next(500, 50000),
|
||||
NbLikes = Random.Shared.Next(50, 5000),
|
||||
Album = album.Name,
|
||||
Commentaires = new List<Commentaire>(),
|
||||
Styles = new List<Style>(stylesTitre),
|
||||
};
|
||||
}
|
||||
|
||||
private static string Trim(string? value, int maxLength, string fallback)
|
||||
{
|
||||
var result = string.IsNullOrWhiteSpace(value) ? fallback : value.Trim();
|
||||
return result.Length > maxLength ? result[..maxLength] : result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Permet de transformer une date recu en chaine de caractere en type DateTime.
|
||||
/// </summary>
|
||||
/// <param name="value">Date en chaine de caractere.</param>
|
||||
/// <returns>DateTime.</returns>
|
||||
private static DateTime ParseDate(string? value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return DateTime.UtcNow;
|
||||
}
|
||||
|
||||
if (DateTime.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out var date))
|
||||
{
|
||||
return DateTime.SpecifyKind(date, DateTimeKind.Utc);
|
||||
}
|
||||
|
||||
if (DateTime.TryParseExact(value, "yyyy-MM", CultureInfo.InvariantCulture, DateTimeStyles.None, out date))
|
||||
{
|
||||
return DateTime.SpecifyKind(date, DateTimeKind.Utc);
|
||||
}
|
||||
|
||||
if (DateTime.TryParseExact(value, "yyyy", CultureInfo.InvariantCulture, DateTimeStyles.None, out date))
|
||||
{
|
||||
return DateTime.SpecifyKind(date, DateTimeKind.Utc);
|
||||
}
|
||||
|
||||
return DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
}
|
||||
156
Webzine.Business/Seeders/SeedDataSpotify.cs
Normal file
156
Webzine.Business/Seeders/SeedDataSpotify.cs
Normal file
@@ -0,0 +1,156 @@
|
||||
namespace Webzine.Business.Seeders;
|
||||
|
||||
using System.Net.Http.Headers;
|
||||
using System.Net.Http.Json;
|
||||
using System.Text;
|
||||
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
using Webzine.Business.DTOs.Spotify;
|
||||
using Webzine.Business.Mappers.Spotify;
|
||||
using Webzine.Entity;
|
||||
using Webzine.Entity.Fixtures;
|
||||
|
||||
public class SeedDataSpotify
|
||||
{
|
||||
private readonly HttpClient httpClient;
|
||||
private readonly SpotifySeederOptions options;
|
||||
|
||||
public SeedDataSpotify(HttpClient httpClient, IOptions<SpotifySeederOptions> optionsAccessor)
|
||||
{
|
||||
this.httpClient = httpClient;
|
||||
this.options = optionsAccessor.Value;
|
||||
}
|
||||
|
||||
public async Task<SeedDataSet> GenererJeuDeDonneesAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Verification des parametres pour l'acces a Spotify.
|
||||
if (string.IsNullOrWhiteSpace(this.options.ClientId) || string.IsNullOrWhiteSpace(this.options.ClientSecret))
|
||||
{
|
||||
throw new InvalidOperationException("Renseignez SpotifySeeder:ClientId et SpotifySeeder:ClientSecret.");
|
||||
}
|
||||
|
||||
var token = await this.GetTokenAsync(cancellationToken);
|
||||
var styles = new Dictionary<string, Style>();
|
||||
var artistes = new List<Artiste>();
|
||||
var titres = new List<Titre>();
|
||||
var commentaires = new List<Commentaire>();
|
||||
var artistIds = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
int nextArtistId = 1;
|
||||
int nextStyleId = 1;
|
||||
int nextTitreId = 1;
|
||||
int nextCommentaireId = 1;
|
||||
|
||||
foreach (var genre in this.options.Genres)
|
||||
{
|
||||
var artistesSpotify = await this.GetAsync<SpotifySearchArtistsResponseDto>(
|
||||
$"https://api.spotify.com/v1/search?q={Uri.EscapeDataString($"genre:\"{genre}\"")}&type=artist&market={this.options.Market}&limit={this.options.ArtistsPerGenre}",
|
||||
token,
|
||||
cancellationToken);
|
||||
|
||||
foreach (var artisteSpotify in artistesSpotify?.Artists?.Items ??[])
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(artisteSpotify.Id) || !artistIds.Add(artisteSpotify.Id))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var stylesTitre = (artisteSpotify.Genres.Count > 0 ? artisteSpotify.Genres :[genre])
|
||||
.Select(g => SpotifyMapper.GetOrCreateStyle(styles, g, ref nextStyleId))
|
||||
.DistinctBy(s => s.IdStyle)
|
||||
.ToList();
|
||||
|
||||
var artiste = SpotifyMapper.ToArtiste(artisteSpotify, stylesTitre, nextArtistId++);
|
||||
artistes.Add(artiste);
|
||||
|
||||
var albums = await this.GetAsync<SpotifyAlbumsResponseDto>(
|
||||
$"https://api.spotify.com/v1/artists/{artisteSpotify.Id}/albums?include_groups=album,single&market={this.options.Market}",
|
||||
token,
|
||||
cancellationToken);
|
||||
|
||||
foreach (var album in albums?.Items.GroupBy(a => a.Name, StringComparer.OrdinalIgnoreCase).Select(g => g.First()) ??[])
|
||||
{
|
||||
var tracks = await this.GetAsync<SpotifyTracksResponseDto>(
|
||||
$"https://api.spotify.com/v1/albums/{album.Id}/tracks?market={this.options.Market}&limit={Math.Clamp(this.options.TracksPerAlbum, 1, 10)}",
|
||||
token,
|
||||
cancellationToken);
|
||||
|
||||
foreach (var track in tracks?.Items ??[])
|
||||
{
|
||||
var titre = SpotifyMapper.ToTitre(track, album, artiste, stylesTitre, nextTitreId++);
|
||||
|
||||
var commentairesTitre = SeedDataLocal.GenererListeCommentaire(
|
||||
titre,
|
||||
0,
|
||||
Math.Clamp(this.options.MaxCommentsPerTrack, 0, 10),
|
||||
nextCommentaireId);
|
||||
|
||||
nextCommentaireId += commentairesTitre.Count;
|
||||
|
||||
titre.Commentaires.AddRange(commentairesTitre);
|
||||
commentaires.AddRange(commentairesTitre);
|
||||
titres.Add(titre);
|
||||
artiste.Titres.Add(titre);
|
||||
|
||||
foreach (var style in titre.Styles)
|
||||
{
|
||||
style.Titres.Add(titre);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new SeedDataSet
|
||||
{
|
||||
Artistes = artistes,
|
||||
Styles = styles.Values.OrderBy(s => s.IdStyle).ToList(),
|
||||
Titres = titres,
|
||||
Commentaires = commentaires,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recuperation du token d'access a Spotify.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">Permet d'annuler la requete HTTP en cas de probleme.</param>
|
||||
/// <returns>Le token d'acces a spotify.</returns>
|
||||
/// <exception cref="InvalidOperationException">Le token spotify est introuvable.</exception>
|
||||
private async Task<string> GetTokenAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
using var request = new HttpRequestMessage(HttpMethod.Post, "https://accounts.spotify.com/api/token");
|
||||
request.Content = new FormUrlEncodedContent(
|
||||
[
|
||||
new KeyValuePair<string, string>("grant_type", "client_credentials"),
|
||||
]);
|
||||
|
||||
var basic = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{this.options.ClientId}:{this.options.ClientSecret}"));
|
||||
request.Headers.Authorization = new AuthenticationHeaderValue("Basic", basic);
|
||||
|
||||
using var response = await this.httpClient.SendAsync(request, cancellationToken);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var payload = await response.Content.ReadFromJsonAsync<SpotifyTokenResponseDto>(cancellationToken: cancellationToken);
|
||||
return payload?.AccessToken ?? throw new InvalidOperationException("Token Spotify introuvable.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recuperation d'information depuis spotify, en formattant
|
||||
/// la requete avec le Bearer Token.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">DTOs Spotify.</typeparam>
|
||||
/// <param name="url">URL Spotify.</param>
|
||||
/// <param name="token">Token Bearer pour authorisation d'acces a l'api.</param>
|
||||
/// <param name="cancellationToken">Permet d'annuler la requete.</param>
|
||||
/// <returns>Reponse Spotify deserialiser.</returns>
|
||||
private async Task<T?> GetAsync<T>(string url, string token, CancellationToken cancellationToken)
|
||||
{
|
||||
// Formattage de la requete HTTP avec le Bearer token.
|
||||
using var request = new HttpRequestMessage(HttpMethod.Get, url);
|
||||
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
|
||||
|
||||
using var response = await this.httpClient.SendAsync(request, cancellationToken);
|
||||
response.EnsureSuccessStatusCode();
|
||||
return await response.Content.ReadFromJsonAsync<T>(cancellationToken: cancellationToken);
|
||||
}
|
||||
}
|
||||
28
Webzine.Business/Seeders/SpotifySeederOptions.cs
Normal file
28
Webzine.Business/Seeders/SpotifySeederOptions.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
namespace Webzine.Business.Seeders;
|
||||
|
||||
public class SpotifySeederOptions
|
||||
{
|
||||
public string ClientId { get; set; } = string.Empty;
|
||||
|
||||
public string ClientSecret { get; set; } = string.Empty;
|
||||
|
||||
public string Market { get; set; } = "FR";
|
||||
|
||||
public List<string> Genres { get; set; } =
|
||||
[
|
||||
"rock",
|
||||
"pop",
|
||||
"jazz",
|
||||
"hip hop",
|
||||
"electronic",
|
||||
"metal",
|
||||
];
|
||||
|
||||
public int ArtistsPerGenre { get; set; } = 4;
|
||||
|
||||
public int AlbumsPerArtist { get; set; } = 2;
|
||||
|
||||
public int TracksPerAlbum { get; set; } = 4;
|
||||
|
||||
public int MaxCommentsPerTrack { get; set; } = 3;
|
||||
}
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Faker.Net" Version="2.0.163" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0" />
|
||||
<PackageReference Include="NLog" Version="6.1.1" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
@@ -21,4 +22,8 @@
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Webzine.Entity\Webzine.Entity.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
Reference in New Issue
Block a user