Добавлены избранные плейлисты
This commit is contained in:
82
PlaylistShared.Api/Controllers/FavoritesController.cs
Normal file
82
PlaylistShared.Api/Controllers/FavoritesController.cs
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using PlaylistShared.Api.Entities;
|
||||||
|
using PlaylistShared.Api.Extensions;
|
||||||
|
using PlaylistShared.Api.Services;
|
||||||
|
using PlaylistShared.Shared;
|
||||||
|
using PlaylistShared.Shared.Shared;
|
||||||
|
|
||||||
|
namespace PlaylistShared.Api.Controllers;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/[controller]")]
|
||||||
|
[Authorize]
|
||||||
|
public class FavoritesController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly UserManager<ApplicationUser> _userManager;
|
||||||
|
private readonly FavoritesService _favoritesService;
|
||||||
|
private readonly SharedPlaylistService _sharedPlaylistService;
|
||||||
|
|
||||||
|
public FavoritesController(
|
||||||
|
UserManager<ApplicationUser> userManager,
|
||||||
|
FavoritesService favoritesService,
|
||||||
|
SharedPlaylistService sharedPlaylistService)
|
||||||
|
{
|
||||||
|
_userManager = userManager;
|
||||||
|
_favoritesService = favoritesService;
|
||||||
|
_sharedPlaylistService = sharedPlaylistService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Получить список избранных плейлистов текущего пользователя.</summary>
|
||||||
|
[HttpGet]
|
||||||
|
public async Task<ActionResult<ApiResponse<List<SharedPlaylistDto>>>> GetFavorites()
|
||||||
|
{
|
||||||
|
var userId = User.GetUserId();
|
||||||
|
var user = await _userManager.FindByIdAsync(userId.ToString());
|
||||||
|
if (user == null) return Unauthorized();
|
||||||
|
|
||||||
|
var favorites = await _favoritesService.GetUserFavoritesAsync(userId);
|
||||||
|
return Ok(ApiResponse<List<SharedPlaylistDto>>.Ok(favorites));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Проверить, добавлен ли плейлист в избранное.</summary>
|
||||||
|
[HttpGet("{shareToken}/check")]
|
||||||
|
public async Task<ActionResult<ApiResponse<bool>>> CheckFavorite(string shareToken)
|
||||||
|
{
|
||||||
|
var userId = User.GetUserId();
|
||||||
|
var user = await _userManager.FindByIdAsync(userId.ToString());
|
||||||
|
if (user == null) return Unauthorized();
|
||||||
|
|
||||||
|
var isFavorite = await _favoritesService.IsFavoriteAsync(userId, shareToken);
|
||||||
|
return Ok(ApiResponse<bool>.Ok(isFavorite));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Добавить плейлист в избранное.</summary>
|
||||||
|
[HttpPost("{shareToken}")]
|
||||||
|
public async Task<ActionResult<ApiResponse<object>>> AddFavorite(string shareToken)
|
||||||
|
{
|
||||||
|
var userId = User.GetUserId();
|
||||||
|
var user = await _userManager.FindByIdAsync(userId.ToString());
|
||||||
|
if (user == null) return Unauthorized();
|
||||||
|
|
||||||
|
var playlist = await _sharedPlaylistService.GetEntityByTokenAsync(shareToken);
|
||||||
|
if (playlist == null)
|
||||||
|
return NotFound(ApiResponse<object>.Fail(new ErrorResponse { StatusCode = 404, Message = "Плейлист не найден" }));
|
||||||
|
|
||||||
|
await _favoritesService.AddFavoriteAsync(userId, shareToken);
|
||||||
|
return Ok(ApiResponse<object>.Ok(new { message = "Плейлист добавлен в избранное" }));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Удалить плейлист из избранного.</summary>
|
||||||
|
[HttpDelete("{shareToken}")]
|
||||||
|
public async Task<ActionResult<ApiResponse<object>>> RemoveFavorite(string shareToken)
|
||||||
|
{
|
||||||
|
var userId = User.GetUserId();
|
||||||
|
var user = await _userManager.FindByIdAsync(userId.ToString());
|
||||||
|
if (user == null) return Unauthorized();
|
||||||
|
|
||||||
|
await _favoritesService.RemoveFavoriteAsync(userId, shareToken);
|
||||||
|
return Ok(ApiResponse<object>.Ok(new { message = "Плейлист удалён из избранного" }));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -82,6 +82,7 @@ public class PlaylistsController : ControllerBase
|
|||||||
|
|
||||||
var dto = new SharePlaylistDto
|
var dto = new SharePlaylistDto
|
||||||
{
|
{
|
||||||
|
YandexPlaylistUuid = playlist.PlaylistUuid,
|
||||||
YandexPlaylistKind = request.Kind,
|
YandexPlaylistKind = request.Kind,
|
||||||
YandexPlaylistOwnerUid = request.OwnerUid,
|
YandexPlaylistOwnerUid = request.OwnerUid,
|
||||||
Title = playlist.Title,
|
Title = playlist.Title,
|
||||||
|
|||||||
@@ -11,8 +11,9 @@ public class ApplicationDbContext : IdentityDbContext<ApplicationUser, IdentityR
|
|||||||
|
|
||||||
public DbSet<SharedPlaylist> SharedPlaylists => Set<SharedPlaylist>();
|
public DbSet<SharedPlaylist> SharedPlaylists => Set<SharedPlaylist>();
|
||||||
public DbSet<TrackAdditionLog> TrackAdditionLogs => Set<TrackAdditionLog>();
|
public DbSet<TrackAdditionLog> TrackAdditionLogs => Set<TrackAdditionLog>();
|
||||||
public DbSet<UserSession> UserSessions => Set<UserSession>();
|
|
||||||
public DbSet<TrackRemovalLog> TrackRemovalLogs => Set<TrackRemovalLog>();
|
public DbSet<TrackRemovalLog> TrackRemovalLogs => Set<TrackRemovalLog>();
|
||||||
|
public DbSet<UserSession> UserSessions => Set<UserSession>();
|
||||||
|
public DbSet<FavoritePlaylist> FavoritePlaylists => Set<FavoritePlaylist>();
|
||||||
|
|
||||||
protected override void OnModelCreating(ModelBuilder builder)
|
protected override void OnModelCreating(ModelBuilder builder)
|
||||||
{
|
{
|
||||||
@@ -26,6 +27,7 @@ public class ApplicationDbContext : IdentityDbContext<ApplicationUser, IdentityR
|
|||||||
.WithMany(u => u.OwnedPlaylists)
|
.WithMany(u => u.OwnedPlaylists)
|
||||||
.HasForeignKey(e => e.CreatorUserId)
|
.HasForeignKey(e => e.CreatorUserId)
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
.OnDelete(DeleteBehavior.Restrict);
|
||||||
|
entity.Property(e => e.YandexPlaylistUuid).IsRequired().HasMaxLength(50);
|
||||||
entity.Property(e => e.YandexPlaylistKind).IsRequired().HasMaxLength(50);
|
entity.Property(e => e.YandexPlaylistKind).IsRequired().HasMaxLength(50);
|
||||||
entity.Property(e => e.YandexPlaylistOwnerUid).IsRequired().HasMaxLength(50);
|
entity.Property(e => e.YandexPlaylistOwnerUid).IsRequired().HasMaxLength(50);
|
||||||
entity.Property(e => e.Title).IsRequired().HasMaxLength(255);
|
entity.Property(e => e.Title).IsRequired().HasMaxLength(255);
|
||||||
@@ -77,5 +79,19 @@ public class ApplicationDbContext : IdentityDbContext<ApplicationUser, IdentityR
|
|||||||
.HasForeignKey(e => e.SessionId)
|
.HasForeignKey(e => e.SessionId)
|
||||||
.OnDelete(DeleteBehavior.Restrict);
|
.OnDelete(DeleteBehavior.Restrict);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
builder.Entity<FavoritePlaylist>(entity =>
|
||||||
|
{
|
||||||
|
entity.HasKey(e => new { e.UserId, e.SharedPlaylistId });
|
||||||
|
entity.HasOne(e => e.User)
|
||||||
|
.WithMany(u => u.FavoritePlaylists)
|
||||||
|
.HasForeignKey(e => e.UserId)
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
entity.HasOne(e => e.SharedPlaylist)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey(e => e.SharedPlaylistId)
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
entity.Property(e => e.AddedAtUtc).IsRequired();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
591
PlaylistShared.Api/Data/Migrations/20260414111229_AddUserFavorites.Designer.cs
generated
Normal file
591
PlaylistShared.Api/Data/Migrations/20260414111229_AddUserFavorites.Designer.cs
generated
Normal file
@@ -0,0 +1,591 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using PlaylistShared.Api.Data;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace PlaylistShared.Api.Data.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(ApplicationDbContext))]
|
||||||
|
[Migration("20260414111229_AddUserFavorites")]
|
||||||
|
partial class AddUserFavorites
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "10.0.5")
|
||||||
|
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||||
|
|
||||||
|
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole<System.Guid>", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<string>("ConcurrencyStamp")
|
||||||
|
.IsConcurrencyToken()
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("nvarchar(256)");
|
||||||
|
|
||||||
|
b.Property<string>("NormalizedName")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("nvarchar(256)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("NormalizedName")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("RoleNameIndex")
|
||||||
|
.HasFilter("[NormalizedName] IS NOT NULL");
|
||||||
|
|
||||||
|
b.ToTable("AspNetRoles", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<System.Guid>", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("ClaimType")
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<string>("ClaimValue")
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<Guid>("RoleId")
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("RoleId");
|
||||||
|
|
||||||
|
b.ToTable("AspNetRoleClaims", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<System.Guid>", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("ClaimType")
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<string>("ClaimValue")
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<Guid>("UserId")
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
|
b.ToTable("AspNetUserClaims", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<System.Guid>", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("LoginProvider")
|
||||||
|
.HasColumnType("nvarchar(450)");
|
||||||
|
|
||||||
|
b.Property<string>("ProviderKey")
|
||||||
|
.HasColumnType("nvarchar(450)");
|
||||||
|
|
||||||
|
b.Property<string>("ProviderDisplayName")
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<Guid>("UserId")
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.HasKey("LoginProvider", "ProviderKey");
|
||||||
|
|
||||||
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
|
b.ToTable("AspNetUserLogins", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<System.Guid>", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("UserId")
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<Guid>("RoleId")
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.HasKey("UserId", "RoleId");
|
||||||
|
|
||||||
|
b.HasIndex("RoleId");
|
||||||
|
|
||||||
|
b.ToTable("AspNetUserRoles", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<System.Guid>", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("UserId")
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<string>("LoginProvider")
|
||||||
|
.HasColumnType("nvarchar(450)");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.HasColumnType("nvarchar(450)");
|
||||||
|
|
||||||
|
b.Property<string>("Value")
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.HasKey("UserId", "LoginProvider", "Name");
|
||||||
|
|
||||||
|
b.ToTable("AspNetUserTokens", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("PlaylistShared.Api.Entities.ApplicationUser", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<int>("AccessFailedCount")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("ConcurrencyStamp")
|
||||||
|
.IsConcurrencyToken()
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<string>("Email")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("nvarchar(256)");
|
||||||
|
|
||||||
|
b.Property<bool>("EmailConfirmed")
|
||||||
|
.HasColumnType("bit");
|
||||||
|
|
||||||
|
b.Property<bool>("LockoutEnabled")
|
||||||
|
.HasColumnType("bit");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset?>("LockoutEnd")
|
||||||
|
.HasColumnType("datetimeoffset");
|
||||||
|
|
||||||
|
b.Property<string>("NormalizedEmail")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("nvarchar(256)");
|
||||||
|
|
||||||
|
b.Property<string>("NormalizedUserName")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("nvarchar(256)");
|
||||||
|
|
||||||
|
b.Property<string>("PasswordHash")
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<string>("PhoneNumber")
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<bool>("PhoneNumberConfirmed")
|
||||||
|
.HasColumnType("bit");
|
||||||
|
|
||||||
|
b.Property<string>("RefreshToken")
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("RefreshTokenExpiryUtc")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<string>("SecurityStamp")
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<bool>("TwoFactorEnabled")
|
||||||
|
.HasColumnType("bit");
|
||||||
|
|
||||||
|
b.Property<string>("UserName")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("nvarchar(256)");
|
||||||
|
|
||||||
|
b.Property<string>("YandexAccessToken")
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<string>("YandexId")
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<string>("YandexRefreshToken")
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("YandexTokenExpiryUtc")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("NormalizedEmail")
|
||||||
|
.HasDatabaseName("EmailIndex");
|
||||||
|
|
||||||
|
b.HasIndex("NormalizedUserName")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("UserNameIndex")
|
||||||
|
.HasFilter("[NormalizedUserName] IS NOT NULL");
|
||||||
|
|
||||||
|
b.ToTable("AspNetUsers", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("PlaylistShared.Api.Entities.FavoritePlaylist", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("UserId")
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<Guid>("SharedPlaylistId")
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<DateTime>("AddedAtUtc")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.HasKey("UserId", "SharedPlaylistId");
|
||||||
|
|
||||||
|
b.HasIndex("SharedPlaylistId");
|
||||||
|
|
||||||
|
b.ToTable("FavoritePlaylists");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("PlaylistShared.Api.Entities.SharedPlaylist", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<int>("AddPermission")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("CoverUrl")
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<Guid>("CreatorUserId")
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<bool>("IsDeleted")
|
||||||
|
.HasColumnType("bit");
|
||||||
|
|
||||||
|
b.Property<int>("PlayPermission")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<int>("RemovePermission")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("ShareToken")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("nvarchar(450)");
|
||||||
|
|
||||||
|
b.Property<string>("Title")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(255)
|
||||||
|
.HasColumnType("nvarchar(255)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedAt")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<int>("ViewPermission")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("YandexPlaylistKind")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("nvarchar(50)");
|
||||||
|
|
||||||
|
b.Property<string>("YandexPlaylistOwnerUid")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("nvarchar(50)");
|
||||||
|
|
||||||
|
b.Property<string>("YandexPlaylistUuid")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("nvarchar(50)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("CreatorUserId");
|
||||||
|
|
||||||
|
b.HasIndex("ShareToken")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("SharedPlaylists");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("PlaylistShared.Api.Entities.TrackAdditionLog", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<DateTime>("AddedAtUtc")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<Guid?>("AddedByUserId")
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<string>("SessionId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("nvarchar(449)");
|
||||||
|
|
||||||
|
b.Property<Guid>("SharedPlaylistId")
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<string>("TrackId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("nvarchar(450)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("AddedByUserId");
|
||||||
|
|
||||||
|
b.HasIndex("SessionId");
|
||||||
|
|
||||||
|
b.HasIndex("SharedPlaylistId", "TrackId");
|
||||||
|
|
||||||
|
b.ToTable("TrackAdditionLogs");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("PlaylistShared.Api.Entities.UserSession", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("SessionId")
|
||||||
|
.HasMaxLength(449)
|
||||||
|
.HasColumnType("nvarchar(449)");
|
||||||
|
|
||||||
|
b.Property<Guid?>("AssociatedUserId")
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<string>("ClientIpAddress")
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("FirstSeenUtc")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<DateTime>("LastSeenUtc")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<string>("UserAgent")
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.HasKey("SessionId");
|
||||||
|
|
||||||
|
b.HasIndex("AssociatedUserId");
|
||||||
|
|
||||||
|
b.ToTable("UserSessions");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("TrackRemovalLog", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<DateTime>("RemovedAtUtc")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<Guid?>("RemovedByUserId")
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<string>("SessionId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("nvarchar(449)");
|
||||||
|
|
||||||
|
b.Property<Guid>("SharedPlaylistId")
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<string>("TrackId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("nvarchar(450)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("RemovedByUserId");
|
||||||
|
|
||||||
|
b.HasIndex("SessionId");
|
||||||
|
|
||||||
|
b.HasIndex("SharedPlaylistId", "TrackId");
|
||||||
|
|
||||||
|
b.ToTable("TrackRemovalLogs");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<System.Guid>", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole<System.Guid>", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("RoleId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<System.Guid>", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("PlaylistShared.Api.Entities.ApplicationUser", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<System.Guid>", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("PlaylistShared.Api.Entities.ApplicationUser", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<System.Guid>", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole<System.Guid>", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("RoleId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("PlaylistShared.Api.Entities.ApplicationUser", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<System.Guid>", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("PlaylistShared.Api.Entities.ApplicationUser", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("PlaylistShared.Api.Entities.FavoritePlaylist", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("PlaylistShared.Api.Entities.SharedPlaylist", "SharedPlaylist")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("SharedPlaylistId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("PlaylistShared.Api.Entities.ApplicationUser", "User")
|
||||||
|
.WithMany("FavoritePlaylists")
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("SharedPlaylist");
|
||||||
|
|
||||||
|
b.Navigation("User");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("PlaylistShared.Api.Entities.SharedPlaylist", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("PlaylistShared.Api.Entities.ApplicationUser", "Creator")
|
||||||
|
.WithMany("OwnedPlaylists")
|
||||||
|
.HasForeignKey("CreatorUserId")
|
||||||
|
.OnDelete(DeleteBehavior.Restrict)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Creator");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("PlaylistShared.Api.Entities.TrackAdditionLog", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("PlaylistShared.Api.Entities.ApplicationUser", "AddedByUser")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("AddedByUserId")
|
||||||
|
.OnDelete(DeleteBehavior.Restrict);
|
||||||
|
|
||||||
|
b.HasOne("PlaylistShared.Api.Entities.UserSession", "Session")
|
||||||
|
.WithMany("TrackAdditionLogs")
|
||||||
|
.HasForeignKey("SessionId")
|
||||||
|
.OnDelete(DeleteBehavior.Restrict)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("PlaylistShared.Api.Entities.SharedPlaylist", "SharedPlaylist")
|
||||||
|
.WithMany("TrackAdditionLogs")
|
||||||
|
.HasForeignKey("SharedPlaylistId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("AddedByUser");
|
||||||
|
|
||||||
|
b.Navigation("Session");
|
||||||
|
|
||||||
|
b.Navigation("SharedPlaylist");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("PlaylistShared.Api.Entities.UserSession", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("PlaylistShared.Api.Entities.ApplicationUser", "User")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("AssociatedUserId")
|
||||||
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
|
||||||
|
b.Navigation("User");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("TrackRemovalLog", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("PlaylistShared.Api.Entities.ApplicationUser", "RemovedByUser")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("RemovedByUserId")
|
||||||
|
.OnDelete(DeleteBehavior.Restrict);
|
||||||
|
|
||||||
|
b.HasOne("PlaylistShared.Api.Entities.UserSession", "Session")
|
||||||
|
.WithMany("TrackRemovalLogs")
|
||||||
|
.HasForeignKey("SessionId")
|
||||||
|
.OnDelete(DeleteBehavior.Restrict)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("PlaylistShared.Api.Entities.SharedPlaylist", "SharedPlaylist")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("SharedPlaylistId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("RemovedByUser");
|
||||||
|
|
||||||
|
b.Navigation("Session");
|
||||||
|
|
||||||
|
b.Navigation("SharedPlaylist");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("PlaylistShared.Api.Entities.ApplicationUser", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("FavoritePlaylists");
|
||||||
|
|
||||||
|
b.Navigation("OwnedPlaylists");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("PlaylistShared.Api.Entities.SharedPlaylist", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("TrackAdditionLogs");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("PlaylistShared.Api.Entities.UserSession", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("TrackAdditionLogs");
|
||||||
|
|
||||||
|
b.Navigation("TrackRemovalLogs");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace PlaylistShared.Api.Data.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddUserFavorites : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "YandexPlaylistUuid",
|
||||||
|
table: "SharedPlaylists",
|
||||||
|
type: "nvarchar(50)",
|
||||||
|
maxLength: 50,
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: "");
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "FavoritePlaylists",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
UserId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||||
|
SharedPlaylistId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||||
|
AddedAtUtc = table.Column<DateTime>(type: "datetime2", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_FavoritePlaylists", x => new { x.UserId, x.SharedPlaylistId });
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_FavoritePlaylists_AspNetUsers_UserId",
|
||||||
|
column: x => x.UserId,
|
||||||
|
principalTable: "AspNetUsers",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_FavoritePlaylists_SharedPlaylists_SharedPlaylistId",
|
||||||
|
column: x => x.SharedPlaylistId,
|
||||||
|
principalTable: "SharedPlaylists",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_FavoritePlaylists_SharedPlaylistId",
|
||||||
|
table: "FavoritePlaylists",
|
||||||
|
column: "SharedPlaylistId");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "FavoritePlaylists");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "YandexPlaylistUuid",
|
||||||
|
table: "SharedPlaylists");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -237,6 +237,24 @@ namespace PlaylistShared.Api.Data.Migrations
|
|||||||
b.ToTable("AspNetUsers", (string)null);
|
b.ToTable("AspNetUsers", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("PlaylistShared.Api.Entities.FavoritePlaylist", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("UserId")
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<Guid>("SharedPlaylistId")
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<DateTime>("AddedAtUtc")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.HasKey("UserId", "SharedPlaylistId");
|
||||||
|
|
||||||
|
b.HasIndex("SharedPlaylistId");
|
||||||
|
|
||||||
|
b.ToTable("FavoritePlaylists");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("PlaylistShared.Api.Entities.SharedPlaylist", b =>
|
modelBuilder.Entity("PlaylistShared.Api.Entities.SharedPlaylist", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
@@ -292,6 +310,11 @@ namespace PlaylistShared.Api.Data.Migrations
|
|||||||
.HasMaxLength(50)
|
.HasMaxLength(50)
|
||||||
.HasColumnType("nvarchar(50)");
|
.HasColumnType("nvarchar(50)");
|
||||||
|
|
||||||
|
b.Property<string>("YandexPlaylistUuid")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("nvarchar(50)");
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
b.HasIndex("CreatorUserId");
|
b.HasIndex("CreatorUserId");
|
||||||
@@ -449,6 +472,25 @@ namespace PlaylistShared.Api.Data.Migrations
|
|||||||
.IsRequired();
|
.IsRequired();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("PlaylistShared.Api.Entities.FavoritePlaylist", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("PlaylistShared.Api.Entities.SharedPlaylist", "SharedPlaylist")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("SharedPlaylistId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("PlaylistShared.Api.Entities.ApplicationUser", "User")
|
||||||
|
.WithMany("FavoritePlaylists")
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("SharedPlaylist");
|
||||||
|
|
||||||
|
b.Navigation("User");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("PlaylistShared.Api.Entities.SharedPlaylist", b =>
|
modelBuilder.Entity("PlaylistShared.Api.Entities.SharedPlaylist", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("PlaylistShared.Api.Entities.ApplicationUser", "Creator")
|
b.HasOne("PlaylistShared.Api.Entities.ApplicationUser", "Creator")
|
||||||
@@ -524,6 +566,8 @@ namespace PlaylistShared.Api.Data.Migrations
|
|||||||
|
|
||||||
modelBuilder.Entity("PlaylistShared.Api.Entities.ApplicationUser", b =>
|
modelBuilder.Entity("PlaylistShared.Api.Entities.ApplicationUser", b =>
|
||||||
{
|
{
|
||||||
|
b.Navigation("FavoritePlaylists");
|
||||||
|
|
||||||
b.Navigation("OwnedPlaylists");
|
b.Navigation("OwnedPlaylists");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -25,4 +25,7 @@ public class ApplicationUser : IdentityUser<Guid>
|
|||||||
|
|
||||||
/// <summary>Плейлисты, созданные пользователем.</summary>
|
/// <summary>Плейлисты, созданные пользователем.</summary>
|
||||||
public ICollection<SharedPlaylist> OwnedPlaylists { get; set; } = new List<SharedPlaylist>();
|
public ICollection<SharedPlaylist> OwnedPlaylists { get; set; } = new List<SharedPlaylist>();
|
||||||
|
|
||||||
|
/// <summary>Избранные плейлисты.</summary>
|
||||||
|
public ICollection<FavoritePlaylist> FavoritePlaylists { get; set; } = new List<FavoritePlaylist>();
|
||||||
}
|
}
|
||||||
13
PlaylistShared.Api/Entities/FavoritePlaylist.cs
Normal file
13
PlaylistShared.Api/Entities/FavoritePlaylist.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
namespace PlaylistShared.Api.Entities;
|
||||||
|
|
||||||
|
/// <summary>Избранный расшаренный плейлист пользователя.</summary>
|
||||||
|
public class FavoritePlaylist
|
||||||
|
{
|
||||||
|
public Guid UserId { get; set; }
|
||||||
|
public Guid SharedPlaylistId { get; set; }
|
||||||
|
public DateTime AddedAtUtc { get; set; }
|
||||||
|
|
||||||
|
// Навигационные свойства
|
||||||
|
public ApplicationUser User { get; set; } = null!;
|
||||||
|
public SharedPlaylist SharedPlaylist { get; set; } = null!;
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ public class SharedPlaylist
|
|||||||
{
|
{
|
||||||
public Guid Id { get; set; }
|
public Guid Id { get; set; }
|
||||||
public Guid CreatorUserId { get; set; }
|
public Guid CreatorUserId { get; set; }
|
||||||
|
public string YandexPlaylistUuid { get; set; } = null!;
|
||||||
public string YandexPlaylistKind { get; set; } = null!;
|
public string YandexPlaylistKind { get; set; } = null!;
|
||||||
public string YandexPlaylistOwnerUid { get; set; } = null!;
|
public string YandexPlaylistOwnerUid { get; set; } = null!;
|
||||||
public string Title { get; set; } = null!;
|
public string Title { get; set; } = null!;
|
||||||
|
|||||||
@@ -96,6 +96,7 @@ public class Program
|
|||||||
builder.Services.AddScoped<SharedPlaylistService>();
|
builder.Services.AddScoped<SharedPlaylistService>();
|
||||||
builder.Services.AddScoped<TrackAdditionLogService>();
|
builder.Services.AddScoped<TrackAdditionLogService>();
|
||||||
builder.Services.AddScoped<TrackRemovalLogService>();
|
builder.Services.AddScoped<TrackRemovalLogService>();
|
||||||
|
builder.Services.AddScoped<FavoritesService>();
|
||||||
builder.Services.AddDataProtection();
|
builder.Services.AddDataProtection();
|
||||||
|
|
||||||
builder.Services.AddHttpClient();
|
builder.Services.AddHttpClient();
|
||||||
|
|||||||
100
PlaylistShared.Api/Services/FavoritesService.cs
Normal file
100
PlaylistShared.Api/Services/FavoritesService.cs
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using PlaylistShared.Api.Data;
|
||||||
|
using PlaylistShared.Api.Entities;
|
||||||
|
using PlaylistShared.Shared.Shared;
|
||||||
|
|
||||||
|
namespace PlaylistShared.Api.Services;
|
||||||
|
|
||||||
|
public class FavoritesService
|
||||||
|
{
|
||||||
|
private readonly ApplicationDbContext _db;
|
||||||
|
private readonly SharedPlaylistService _sharedPlaylistService;
|
||||||
|
|
||||||
|
public FavoritesService(ApplicationDbContext db, SharedPlaylistService sharedPlaylistService)
|
||||||
|
{
|
||||||
|
_db = db;
|
||||||
|
_sharedPlaylistService = sharedPlaylistService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> IsFavoriteAsync(Guid userId, string shareToken)
|
||||||
|
{
|
||||||
|
var playlist = await _sharedPlaylistService.GetEntityByTokenAsync(shareToken);
|
||||||
|
if (playlist == null) return false;
|
||||||
|
return await _db.FavoritePlaylists
|
||||||
|
.AnyAsync(f => f.UserId == userId && f.SharedPlaylistId == playlist.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task AddFavoriteAsync(Guid userId, string shareToken)
|
||||||
|
{
|
||||||
|
var playlist = await _sharedPlaylistService.GetEntityByTokenAsync(shareToken);
|
||||||
|
if (playlist == null)
|
||||||
|
throw new ArgumentException("Playlist not found");
|
||||||
|
|
||||||
|
var exists = await _db.FavoritePlaylists
|
||||||
|
.AnyAsync(f => f.UserId == userId && f.SharedPlaylistId == playlist.Id);
|
||||||
|
if (exists) return;
|
||||||
|
|
||||||
|
var favorite = new FavoritePlaylist
|
||||||
|
{
|
||||||
|
UserId = userId,
|
||||||
|
SharedPlaylistId = playlist.Id,
|
||||||
|
AddedAtUtc = DateTime.UtcNow
|
||||||
|
};
|
||||||
|
_db.FavoritePlaylists.Add(favorite);
|
||||||
|
await _db.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task RemoveFavoriteAsync(Guid userId, string shareToken)
|
||||||
|
{
|
||||||
|
var playlist = await _sharedPlaylistService.GetEntityByTokenAsync(shareToken);
|
||||||
|
if (playlist == null) return;
|
||||||
|
|
||||||
|
var favorite = await _db.FavoritePlaylists
|
||||||
|
.FirstOrDefaultAsync(f => f.UserId == userId && f.SharedPlaylistId == playlist.Id);
|
||||||
|
if (favorite != null)
|
||||||
|
{
|
||||||
|
_db.FavoritePlaylists.Remove(favorite);
|
||||||
|
await _db.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<SharedPlaylistDto>> GetUserFavoritesAsync(Guid userId)
|
||||||
|
{
|
||||||
|
var favoritePlaylists = await _db.FavoritePlaylists
|
||||||
|
.Include(f => f.SharedPlaylist)
|
||||||
|
.ThenInclude(sp => sp.Creator)
|
||||||
|
.Where(f => f.UserId == userId)
|
||||||
|
.OrderByDescending(f => f.AddedAtUtc)
|
||||||
|
.Select(f => f.SharedPlaylist)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
// Маппинг в DTO (можно использовать AutoMapper, но для простоты сделаем вручную)
|
||||||
|
return favoritePlaylists.Select(sp => new SharedPlaylistDto
|
||||||
|
{
|
||||||
|
Id = sp.Id,
|
||||||
|
CreatorUserId = sp.CreatorUserId,
|
||||||
|
YandexPlaylistUuid = sp.YandexPlaylistUuid,
|
||||||
|
YandexPlaylistKind = sp.YandexPlaylistKind,
|
||||||
|
YandexPlaylistOwnerUid = sp.YandexPlaylistOwnerUid,
|
||||||
|
Title = sp.Title,
|
||||||
|
Description = sp.Description,
|
||||||
|
CoverUrl = sp.CoverUrl,
|
||||||
|
CreatedAt = sp.CreatedAt,
|
||||||
|
UpdatedAt = sp.UpdatedAt,
|
||||||
|
IsDeleted = sp.IsDeleted,
|
||||||
|
ShareToken = sp.ShareToken,
|
||||||
|
ViewPermission = sp.ViewPermission,
|
||||||
|
PlayPermission = sp.PlayPermission,
|
||||||
|
AddPermission = sp.AddPermission,
|
||||||
|
RemovePermission = sp.RemovePermission,
|
||||||
|
Creator = sp.Creator != null ? new Shared.Auth.ApplicationUserDto
|
||||||
|
{
|
||||||
|
Id = sp.Creator.Id,
|
||||||
|
UserName = sp.Creator.UserName,
|
||||||
|
Email = sp.Creator.Email,
|
||||||
|
YandexId = sp.Creator.YandexId,
|
||||||
|
DisplayName = sp.Creator.UserName
|
||||||
|
} : null
|
||||||
|
}).ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -27,6 +27,7 @@ public class SharedPlaylistService
|
|||||||
{
|
{
|
||||||
Id = Guid.NewGuid(),
|
Id = Guid.NewGuid(),
|
||||||
CreatorUserId = creatorUserId,
|
CreatorUserId = creatorUserId,
|
||||||
|
YandexPlaylistUuid = dto.YandexPlaylistUuid,
|
||||||
YandexPlaylistKind = dto.YandexPlaylistKind,
|
YandexPlaylistKind = dto.YandexPlaylistKind,
|
||||||
YandexPlaylistOwnerUid = dto.YandexPlaylistOwnerUid,
|
YandexPlaylistOwnerUid = dto.YandexPlaylistOwnerUid,
|
||||||
Title = dto.Title,
|
Title = dto.Title,
|
||||||
|
|||||||
@@ -2,8 +2,9 @@
|
|||||||
<MudNavLink Href="/" Match="NavLinkMatch.All" Icon="@Icons.Material.Filled.Home">Главная</MudNavLink>
|
<MudNavLink Href="/" Match="NavLinkMatch.All" Icon="@Icons.Material.Filled.Home">Главная</MudNavLink>
|
||||||
<AuthorizeView>
|
<AuthorizeView>
|
||||||
<Authorized>
|
<Authorized>
|
||||||
<MudNavLink Href="/my-playlists" Icon="@Icons.Material.Filled.QueueMusic">Мои плейлисты</MudNavLink>
|
|
||||||
<MudNavLink Href="/profile" Icon="@Icons.Material.Filled.Person">Профиль</MudNavLink>
|
<MudNavLink Href="/profile" Icon="@Icons.Material.Filled.Person">Профиль</MudNavLink>
|
||||||
|
<MudNavLink Href="/my-playlists" Icon="@Icons.Material.Filled.QueueMusic">Мои плейлисты</MudNavLink>
|
||||||
|
<MudNavLink Href="/favorites" Icon="@Icons.Material.Filled.Favorite">Избранное</MudNavLink>
|
||||||
</Authorized>
|
</Authorized>
|
||||||
</AuthorizeView>
|
</AuthorizeView>
|
||||||
</MudNavMenu>
|
</MudNavMenu>
|
||||||
112
PlaylistShared.Pwa/Pages/Favorites.razor
Normal file
112
PlaylistShared.Pwa/Pages/Favorites.razor
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
@page "/favorites"
|
||||||
|
@attribute [Authorize]
|
||||||
|
@using PlaylistShared.Shared.DTO
|
||||||
|
@inject HttpClient Http
|
||||||
|
@inject ISnackbar Snackbar
|
||||||
|
@inject NavigationManager Navigation
|
||||||
|
|
||||||
|
<MudContainer MaxWidth="MaxWidth.Large" Class="mt-8">
|
||||||
|
<MudCard>
|
||||||
|
<MudCardHeader>
|
||||||
|
<CardHeaderContent>
|
||||||
|
<MudText Typo="Typo.h5">Избранные плейлисты</MudText>
|
||||||
|
<MudText Typo="Typo.body2">Расшаренные плейлисты, которые вы добавили в избранное</MudText>
|
||||||
|
</CardHeaderContent>
|
||||||
|
<CardHeaderActions>
|
||||||
|
<MudIconButton Icon="@Icons.Material.Filled.Refresh" OnClick="LoadFavorites" />
|
||||||
|
</CardHeaderActions>
|
||||||
|
</MudCardHeader>
|
||||||
|
<MudCardContent>
|
||||||
|
@if (_loading)
|
||||||
|
{
|
||||||
|
<MudProgressCircular Indeterminate />
|
||||||
|
}
|
||||||
|
else if (_favorites == null || !_favorites.Any())
|
||||||
|
{
|
||||||
|
<MudAlert Severity="Severity.Info">
|
||||||
|
У вас пока нет избранных плейлистов. Перейдите на страницу расшаренного плейлиста и нажмите ★, чтобы добавить.
|
||||||
|
</MudAlert>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudTable Items="@_favorites">
|
||||||
|
<HeaderContent>
|
||||||
|
<MudTh>Название</MudTh>
|
||||||
|
<MudTh>Владелец</MudTh>
|
||||||
|
<MudTh>Треков</MudTh>
|
||||||
|
<MudTh></MudTh>
|
||||||
|
</HeaderContent>
|
||||||
|
<RowTemplate>
|
||||||
|
<MudTd DataLabel="Название">
|
||||||
|
<MudLink Href="@($"/shared/{context.ShareToken}")" Underline="Underline.Hover">
|
||||||
|
@context.Title
|
||||||
|
</MudLink>
|
||||||
|
</MudTd>
|
||||||
|
<MudTd DataLabel="Владелец">@context.Creator?.UserName</MudTd>
|
||||||
|
<MudTd DataLabel="Треков">@context.TrackCount</MudTd>
|
||||||
|
<MudTd DataLabel="">
|
||||||
|
<MudIconButton Icon="@Icons.Material.Filled.Delete"
|
||||||
|
Color="Color.Error"
|
||||||
|
OnClick="() => RemoveFromFavorites(context)"
|
||||||
|
Title="Удалить из избранного" />
|
||||||
|
</MudTd>
|
||||||
|
</RowTemplate>
|
||||||
|
</MudTable>
|
||||||
|
}
|
||||||
|
</MudCardContent>
|
||||||
|
</MudCard>
|
||||||
|
</MudContainer>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private List<SharedPlaylistDto> _favorites = new();
|
||||||
|
private bool _loading = true;
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
await LoadFavorites();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LoadFavorites()
|
||||||
|
{
|
||||||
|
_loading = true;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var response = await Http.GetFromJsonAsync<ApiResponse<List<SharedPlaylistDto>>>("/api/favorites");
|
||||||
|
if (response?.Success == true)
|
||||||
|
_favorites = response.Data ?? new();
|
||||||
|
else
|
||||||
|
Snackbar.Add(response?.Error?.Message ?? "Ошибка загрузки избранного", Severity.Error);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Snackbar.Add($"Ошибка: {ex.Message}", Severity.Error);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_loading = false;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task RemoveFromFavorites(SharedPlaylistDto playlist)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var response = await Http.DeleteAsync($"/api/favorites/{playlist.ShareToken}");
|
||||||
|
if (response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
Snackbar.Add($"Плейлист \"{playlist.Title}\" удалён из избранного", Severity.Success);
|
||||||
|
await LoadFavorites();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var error = await response.Content.ReadFromJsonAsync<ApiResponse<object>>();
|
||||||
|
Snackbar.Add(error?.Error?.Message ?? "Ошибка удаления", Severity.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Snackbar.Add($"Ошибка: {ex.Message}", Severity.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -27,10 +27,21 @@
|
|||||||
<div style="display: flex; gap: 16px; align-items: center;">
|
<div style="display: flex; gap: 16px; align-items: center;">
|
||||||
@if (!string.IsNullOrEmpty(_playlist.CoverUrl))
|
@if (!string.IsNullOrEmpty(_playlist.CoverUrl))
|
||||||
{
|
{
|
||||||
<MudImage Src="@FormatCoverUrl(_playlist.CoverUrl)" Height="80" Width="80" Class="rounded" />
|
<MudImage Src="@FormatCoverUrl(_playlist.CoverUrl, "80x80")" Height="80" Width="80" Class="rounded" />
|
||||||
}
|
}
|
||||||
<div>
|
<div>
|
||||||
<MudText Typo="Typo.h5">@_playlist.Title</MudText>
|
<div style="display: flex; align-items: center; gap: 8px;">
|
||||||
|
<MudLink Href="@($"https://music.yandex.ru/playlists/{_playlist.YandexPlaylistUuid}")" Target="_blank" Underline="Underline.Hover">
|
||||||
|
@_playlist.Title
|
||||||
|
<MudIcon Icon="@Icons.Material.Filled.OpenInNew" Size="Size.Small" Class="ml-1" />
|
||||||
|
</MudLink>
|
||||||
|
|
||||||
|
<MudIconButton Icon="@(_isFavorite? Icons.Material.Filled.Favorite : Icons.Material.Outlined.FavoriteBorder)"
|
||||||
|
Color="Color.Error"
|
||||||
|
OnClick="ToggleFavorite"
|
||||||
|
Disabled="_favoriteLoading"
|
||||||
|
Size="Size.Medium" />
|
||||||
|
</div>
|
||||||
<MudText Typo="Typo.body2" Color="Color.Secondary">Владелец: @_playlist.Creator?.UserName</MudText>
|
<MudText Typo="Typo.body2" Color="Color.Secondary">Владелец: @_playlist.Creator?.UserName</MudText>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -212,6 +223,9 @@
|
|||||||
private bool _savingPermissions;
|
private bool _savingPermissions;
|
||||||
private string? _currentUserId;
|
private string? _currentUserId;
|
||||||
|
|
||||||
|
private bool _isFavorite = false;
|
||||||
|
private bool _favoriteLoading = false;
|
||||||
|
|
||||||
private List<YandexTrackDisplay> _tracks = new();
|
private List<YandexTrackDisplay> _tracks = new();
|
||||||
private bool _tracksLoading;
|
private bool _tracksLoading;
|
||||||
|
|
||||||
@@ -226,6 +240,67 @@
|
|||||||
await LoadPlaylist();
|
await LoadPlaylist();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task CheckFavoriteStatus()
|
||||||
|
{
|
||||||
|
if (!_isAuthenticated || _playlist == null) return;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var response = await Http.GetFromJsonAsync<ApiResponse<bool>>($"/api/favorites/{Token}/check");
|
||||||
|
if (response?.Success == true)
|
||||||
|
_isFavorite = response.Data;
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ToggleFavorite()
|
||||||
|
{
|
||||||
|
if (!_isAuthenticated)
|
||||||
|
{
|
||||||
|
Snackbar.Add("Добавление в избранное доступно только авторизованным пользователям", Severity.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_favoriteLoading = true;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_isFavorite)
|
||||||
|
{
|
||||||
|
var response = await Http.DeleteAsync($"/api/favorites/{Token}");
|
||||||
|
if (response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
_isFavorite = false;
|
||||||
|
Snackbar.Add("Плейлист удалён из избранного", Severity.Success);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Snackbar.Add("Ошибка удаления из избранного", Severity.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var response = await Http.PostAsync($"/api/favorites/{Token}", null);
|
||||||
|
if (response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
_isFavorite = true;
|
||||||
|
Snackbar.Add("Плейлист добавлен в избранное", Severity.Success);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Snackbar.Add("Ошибка добавления в избранное", Severity.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Snackbar.Add($"Ошибка: {ex.Message}", Severity.Error);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_favoriteLoading = false;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task ConfigurePermissions()
|
private async Task ConfigurePermissions()
|
||||||
{
|
{
|
||||||
if (_playlist is null)
|
if (_playlist is null)
|
||||||
@@ -275,8 +350,8 @@
|
|||||||
_playlist = response.Data;
|
_playlist = response.Data;
|
||||||
|
|
||||||
await ConfigurePermissions();
|
await ConfigurePermissions();
|
||||||
|
|
||||||
await LoadTracks();
|
await LoadTracks();
|
||||||
|
await CheckFavoriteStatus();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -6,6 +6,10 @@ namespace PlaylistShared.Shared.Playlist;
|
|||||||
/// <summary>Запрос на создание нового шеринг-плейлиста.</summary>
|
/// <summary>Запрос на создание нового шеринг-плейлиста.</summary>
|
||||||
public class SharePlaylistDto
|
public class SharePlaylistDto
|
||||||
{
|
{
|
||||||
|
/// <summary>Идентификатор плейлиста в Яндекс.Музыке (guid).</summary>
|
||||||
|
[JsonPropertyName("yandexPlaylistId")]
|
||||||
|
public string YandexPlaylistUuid { get; set; } = null!;
|
||||||
|
|
||||||
/// <summary>Идентификатор плейлиста в Яндекс.Музыке (kind).</summary>
|
/// <summary>Идентификатор плейлиста в Яндекс.Музыке (kind).</summary>
|
||||||
[JsonPropertyName("yandexPlaylistKind")]
|
[JsonPropertyName("yandexPlaylistKind")]
|
||||||
public string YandexPlaylistKind { get; set; } = null!;
|
public string YandexPlaylistKind { get; set; } = null!;
|
||||||
|
|||||||
@@ -15,6 +15,10 @@ public class SharedPlaylistDto
|
|||||||
[JsonPropertyName("creatorUserId")]
|
[JsonPropertyName("creatorUserId")]
|
||||||
public Guid CreatorUserId { get; set; }
|
public Guid CreatorUserId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Uuid на яндекс плейлист</summary>
|
||||||
|
[JsonPropertyName("yandexPlaylistUuid")]
|
||||||
|
public string? YandexPlaylistUuid { get; set; }
|
||||||
|
|
||||||
/// <summary>Идентификатор плейлиста в Яндекс.Музыке (kind).</summary>
|
/// <summary>Идентификатор плейлиста в Яндекс.Музыке (kind).</summary>
|
||||||
[JsonPropertyName("yandexPlaylistKind")]
|
[JsonPropertyName("yandexPlaylistKind")]
|
||||||
public string YandexPlaylistKind { get; set; } = null!;
|
public string YandexPlaylistKind { get; set; } = null!;
|
||||||
|
|||||||
Reference in New Issue
Block a user