Compare commits
6 Commits
4b3036364b
...
b46e3a0715
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b46e3a0715 | ||
|
|
68887284c1 | ||
|
|
9e8bb0db75 | ||
|
|
dcb2efbedb | ||
|
|
8230951839 | ||
|
|
164cf455fd |
@@ -13,15 +13,18 @@ public class AudioController : ControllerBase
|
||||
{
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
private readonly YandexMusicService _yandexService;
|
||||
private readonly SharedPlaylistService _sharedService;
|
||||
private readonly JwtService _jwtService;
|
||||
|
||||
public AudioController(
|
||||
UserManager<ApplicationUser> userManager,
|
||||
YandexMusicService yandexService,
|
||||
SharedPlaylistService sharedService,
|
||||
JwtService jwtService)
|
||||
{
|
||||
_userManager = userManager;
|
||||
_yandexService = yandexService;
|
||||
_sharedService = sharedService;
|
||||
_jwtService = jwtService;
|
||||
}
|
||||
|
||||
@@ -29,17 +32,18 @@ public class AudioController : ControllerBase
|
||||
/// Потоковое воспроизведение трека из Яндекс.Музыки.
|
||||
/// </summary>
|
||||
/// <param name="trackId">ID трека (например, "21696942").</param>
|
||||
/// <param name="access_token">gwt пользователя</param>
|
||||
/// <param name="shared_id">ID расшаренного плейлиста</param>
|
||||
[HttpGet("track/{trackId}")]
|
||||
[AllowAnonymous]
|
||||
public async Task<IActionResult> StreamTrack(string trackId, [FromQuery] string? access_token = null)
|
||||
public async Task<IActionResult> StreamTrack(string trackId, [FromQuery] string? access_token = null, [FromQuery] string? shared_id = null)
|
||||
{
|
||||
var user = await GetUserFromToken(access_token);
|
||||
if (user == null)
|
||||
return Unauthorized();
|
||||
if (user == null) user = await GetUserFromSharedPlaylistId(shared_id);
|
||||
if (user == null) return Unauthorized();
|
||||
|
||||
var streamUrl = await _yandexService.GetTrackFileUrlAsync(user, trackId);
|
||||
if (string.IsNullOrEmpty(streamUrl))
|
||||
return NotFound();
|
||||
if (string.IsNullOrEmpty(streamUrl)) return NotFound();
|
||||
|
||||
var httpClient = new HttpClient();
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, streamUrl);
|
||||
@@ -56,11 +60,11 @@ public class AudioController : ControllerBase
|
||||
Response.ContentType = response.Content.Headers.ContentType?.ToString() ?? "audio/mpeg";
|
||||
|
||||
if (response.Content.Headers.Contains("Content-Range"))
|
||||
Response.Headers.Add("Content-Range", response.Content.Headers.ContentRange?.ToString());
|
||||
Response.Headers.Append("Content-Range", response.Content.Headers.ContentRange?.ToString());
|
||||
if (response.Headers.Contains("Accept-Ranges"))
|
||||
Response.Headers.Add("Accept-Ranges", response.Headers.AcceptRanges?.ToString());
|
||||
Response.Headers.Append("Accept-Ranges", response.Headers.AcceptRanges?.ToString());
|
||||
if (response.Content.Headers.Contains("Content-Length"))
|
||||
Response.Headers.Add("Content-Length", response.Content.Headers.ContentLength?.ToString());
|
||||
Response.Headers.Append("Content-Length", response.Content.Headers.ContentLength?.ToString());
|
||||
|
||||
await response.Content.CopyToAsync(Response.Body);
|
||||
return new EmptyResult();
|
||||
@@ -68,17 +72,28 @@ public class AudioController : ControllerBase
|
||||
|
||||
private async Task<ApplicationUser?> GetUserFromToken(string? token)
|
||||
{
|
||||
if (string.IsNullOrEmpty(token))
|
||||
return null;
|
||||
if (string.IsNullOrEmpty(token)) return null;
|
||||
|
||||
var principal = _jwtService.ValidateToken(token);
|
||||
if (principal == null)
|
||||
return null;
|
||||
if (principal == null) return null;
|
||||
|
||||
var userId = principal.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
if (string.IsNullOrEmpty(userId))
|
||||
return null;
|
||||
if (string.IsNullOrEmpty(userId)) return null;
|
||||
|
||||
return await _userManager.FindByIdAsync(userId);
|
||||
}
|
||||
|
||||
private async Task<ApplicationUser?> GetUserFromSharedPlaylistId(string? sharedId)
|
||||
{
|
||||
if (string.IsNullOrEmpty(sharedId)) return null;
|
||||
|
||||
var playlist = await _sharedService.GetEntityByTokenAsync(sharedId);
|
||||
|
||||
if (playlist == null) return null;
|
||||
|
||||
if (!await _sharedService.CanPlayEveryoneAsync(playlist)) return null;
|
||||
|
||||
return await _userManager.FindByIdAsync(playlist.CreatorUserId.ToString());
|
||||
|
||||
}
|
||||
}
|
||||
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,13 +82,15 @@ public class PlaylistsController : ControllerBase
|
||||
|
||||
var dto = new SharePlaylistDto
|
||||
{
|
||||
YandexPlaylistUuid = playlist.PlaylistUuid,
|
||||
YandexPlaylistKind = request.Kind,
|
||||
YandexPlaylistOwnerUid = request.OwnerUid,
|
||||
Title = playlist.Title,
|
||||
Description = playlist.Description,
|
||||
ViewPermission = ViewPermission.Everyone,
|
||||
PlayPermission = ViewPermission.Everyone,
|
||||
AddPermission = EditPermission.AuthorizedOnly,
|
||||
RemovePermission = EditPermission.AddedByUserOnly
|
||||
RemovePermission = EditPermission.AddedByUserOnly,
|
||||
};
|
||||
|
||||
var result = await _sharedService.CreateAsync(userId, dto);
|
||||
|
||||
@@ -1,23 +1,33 @@
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.DataProtection.EntityFrameworkCore;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using PlaylistShared.Api.Entities;
|
||||
|
||||
namespace PlaylistShared.Api.Data;
|
||||
|
||||
public class ApplicationDbContext : IdentityDbContext<ApplicationUser, IdentityRole<Guid>, Guid>
|
||||
public class ApplicationDbContext : IdentityDbContext<ApplicationUser, IdentityRole<Guid>, Guid>, IDataProtectionKeyContext
|
||||
{
|
||||
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { }
|
||||
|
||||
public DbSet<FavoritePlaylist> FavoritePlaylists => Set<FavoritePlaylist>();
|
||||
|
||||
public DbSet<SharedPlaylist> SharedPlaylists => Set<SharedPlaylist>();
|
||||
public DbSet<TrackAdditionLog> TrackAdditionLogs => Set<TrackAdditionLog>();
|
||||
public DbSet<UserSession> UserSessions => Set<UserSession>();
|
||||
public DbSet<TrackRemovalLog> TrackRemovalLogs => Set<TrackRemovalLog>();
|
||||
public DbSet<UserSession> UserSessions => Set<UserSession>();
|
||||
public DbSet<DataProtectionKey> DataProtectionKeys { get; set; }
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder builder)
|
||||
{
|
||||
base.OnModelCreating(builder);
|
||||
|
||||
builder.Entity<DataProtectionKey>(entity =>
|
||||
{
|
||||
entity.HasKey(e => e.Id);
|
||||
entity.Property(e => e.FriendlyName).IsRequired();
|
||||
});
|
||||
|
||||
builder.Entity<SharedPlaylist>(entity =>
|
||||
{
|
||||
entity.HasKey(e => e.Id);
|
||||
@@ -26,6 +36,7 @@ public class ApplicationDbContext : IdentityDbContext<ApplicationUser, IdentityR
|
||||
.WithMany(u => u.OwnedPlaylists)
|
||||
.HasForeignKey(e => e.CreatorUserId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
entity.Property(e => e.YandexPlaylistUuid).IsRequired().HasMaxLength(50);
|
||||
entity.Property(e => e.YandexPlaylistKind).IsRequired().HasMaxLength(50);
|
||||
entity.Property(e => e.YandexPlaylistOwnerUid).IsRequired().HasMaxLength(50);
|
||||
entity.Property(e => e.Title).IsRequired().HasMaxLength(255);
|
||||
@@ -77,5 +88,19 @@ public class ApplicationDbContext : IdentityDbContext<ApplicationUser, IdentityR
|
||||
.HasForeignKey(e => e.SessionId)
|
||||
.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();
|
||||
});
|
||||
}
|
||||
}
|
||||
547
PlaylistShared.Api/Data/Migrations/20260414094124_AddSharedPermissions.Designer.cs
generated
Normal file
547
PlaylistShared.Api/Data/Migrations/20260414094124_AddSharedPermissions.Designer.cs
generated
Normal file
@@ -0,0 +1,547 @@
|
||||
// <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("20260414094124_AddSharedPermissions")]
|
||||
partial class AddSharedPermissions
|
||||
{
|
||||
/// <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.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.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.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("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,29 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace PlaylistShared.Api.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddSharedPermissions : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "PlayPermission",
|
||||
table: "SharedPlaylists",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "PlayPermission",
|
||||
table: "SharedPlaylists");
|
||||
}
|
||||
}
|
||||
}
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
611
PlaylistShared.Api/Data/Migrations/20260414121754_AddDataProtectionKeys.Designer.cs
generated
Normal file
611
PlaylistShared.Api/Data/Migrations/20260414121754_AddDataProtectionKeys.Designer.cs
generated
Normal file
@@ -0,0 +1,611 @@
|
||||
// <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("20260414121754_AddDataProtectionKeys")]
|
||||
partial class AddDataProtectionKeys
|
||||
{
|
||||
/// <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.DataProtection.EntityFrameworkCore.DataProtectionKey", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("FriendlyName")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Xml")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("DataProtectionKeys");
|
||||
});
|
||||
|
||||
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,35 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace PlaylistShared.Api.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddDataProtectionKeys : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "DataProtectionKeys",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("SqlServer:Identity", "1, 1"),
|
||||
FriendlyName = table.Column<string>(type: "nvarchar(max)", nullable: false),
|
||||
Xml = table.Column<string>(type: "nvarchar(max)", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_DataProtectionKeys", x => x.Id);
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "DataProtectionKeys");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,26 @@ namespace PlaylistShared.Api.Data.Migrations
|
||||
|
||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("FriendlyName")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Xml")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("DataProtectionKeys");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole<System.Guid>", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
@@ -237,6 +257,24 @@ namespace PlaylistShared.Api.Data.Migrations
|
||||
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")
|
||||
@@ -261,6 +299,9 @@ namespace PlaylistShared.Api.Data.Migrations
|
||||
b.Property<bool>("IsDeleted")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<int>("PlayPermission")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("RemovePermission")
|
||||
.HasColumnType("int");
|
||||
|
||||
@@ -289,6 +330,11 @@ namespace PlaylistShared.Api.Data.Migrations
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.Property<string>("YandexPlaylistUuid")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CreatorUserId");
|
||||
@@ -446,6 +492,25 @@ namespace PlaylistShared.Api.Data.Migrations
|
||||
.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")
|
||||
@@ -521,6 +586,8 @@ namespace PlaylistShared.Api.Data.Migrations
|
||||
|
||||
modelBuilder.Entity("PlaylistShared.Api.Entities.ApplicationUser", b =>
|
||||
{
|
||||
b.Navigation("FavoritePlaylists");
|
||||
|
||||
b.Navigation("OwnedPlaylists");
|
||||
});
|
||||
|
||||
|
||||
@@ -25,4 +25,7 @@ public class ApplicationUser : IdentityUser<Guid>
|
||||
|
||||
/// <summary>Плейлисты, созданные пользователем.</summary>
|
||||
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 CreatorUserId { get; set; }
|
||||
public string YandexPlaylistUuid { get; set; } = null!;
|
||||
public string YandexPlaylistKind { get; set; } = null!;
|
||||
public string YandexPlaylistOwnerUid { get; set; } = null!;
|
||||
public string Title { get; set; } = null!;
|
||||
@@ -17,6 +18,7 @@ public class SharedPlaylist
|
||||
public bool IsDeleted { get; set; }
|
||||
public string ShareToken { get; set; } = null!;
|
||||
public ViewPermission ViewPermission { get; set; }
|
||||
public ViewPermission PlayPermission { get; set; }
|
||||
public EditPermission AddPermission { get; set; }
|
||||
public EditPermission RemovePermission { get; set; }
|
||||
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
using AutoMapper;
|
||||
using PlaylistShared.Api.Entities;
|
||||
using PlaylistShared.Shared.Auth;
|
||||
using PlaylistShared.Shared.Shared;
|
||||
|
||||
namespace PlaylistShared.Api.Mapping;
|
||||
|
||||
public class AppMappingProfile : Profile
|
||||
{
|
||||
public AppMappingProfile()
|
||||
{
|
||||
CreateMap<SharedPlaylist, SharedPlaylistDto>()
|
||||
.ForMember(dest => dest.Creator, opt => opt.MapFrom(src => src.Creator));
|
||||
CreateMap<ApplicationUser, ApplicationUserDto>();
|
||||
}
|
||||
}
|
||||
@@ -10,9 +10,9 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AutoMapper" Version="16.1.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.5" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="10.0.5" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.DataProtection.EntityFrameworkCore" Version="10.0.5" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="10.0.5" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="10.0.5" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.5" />
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.DataProtection;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using PlaylistShared.Api.Data;
|
||||
using PlaylistShared.Api.Entities;
|
||||
using PlaylistShared.Api.Mapping;
|
||||
using PlaylistShared.Api.Services;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Text;
|
||||
@@ -23,7 +23,8 @@ public class Program
|
||||
|
||||
// DbContext
|
||||
builder.Services.AddDbContext<ApplicationDbContext>(options =>
|
||||
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));// Identity
|
||||
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
|
||||
// Identity
|
||||
builder.Services.AddIdentity<ApplicationUser, IdentityRole<Guid>>(options =>
|
||||
{
|
||||
options.User.RequireUniqueEmail = true;
|
||||
@@ -88,30 +89,24 @@ public class Program
|
||||
|
||||
builder.Services.AddHttpContextAccessor();
|
||||
builder.Services.AddAuthorization();
|
||||
builder.Services.AddAutoMapper(t => t.AddProfile<AppMappingProfile>());
|
||||
builder.Services.AddScoped<JwtService>();
|
||||
builder.Services.AddScoped<UserSessionService>();
|
||||
builder.Services.AddScoped<YandexMusicService>();
|
||||
builder.Services.AddScoped<SharedPlaylistService>();
|
||||
builder.Services.AddScoped<TrackAdditionLogService>();
|
||||
builder.Services.AddScoped<TrackRemovalLogService>();
|
||||
builder.Services.AddDataProtection();
|
||||
builder.Services.AddScoped<FavoritesService>();
|
||||
builder.Services.AddDataProtection()
|
||||
.PersistKeysToDbContext<ApplicationDbContext>()
|
||||
.SetApplicationName("PlaylistShared.Api");
|
||||
|
||||
builder.Services.AddHttpClient();
|
||||
|
||||
builder.Services.AddCors(options =>
|
||||
{
|
||||
options.AddPolicy("Development", policy =>
|
||||
{
|
||||
policy.WithOrigins("http://localhost:5053", "https://localhost:7225", "http://localhost:5181", "https://api.playlistshare.frigat.duckdns.org", "https://playlistshare.frigat.duckdns.org")
|
||||
.AllowAnyMethod()
|
||||
.AllowAnyHeader()
|
||||
.AllowCredentials();
|
||||
});
|
||||
|
||||
options.AddPolicy("Production", policy =>
|
||||
{
|
||||
policy.WithOrigins("https://api.playlistshare.frigat.duckdns.org", "https://playlistshare.frigat.duckdns.org")
|
||||
policy.WithOrigins(builder.Configuration.GetSection("Cors:Origins").Get<string[]>())
|
||||
.AllowAnyMethod()
|
||||
.AllowAnyHeader()
|
||||
.AllowCredentials();
|
||||
@@ -131,17 +126,14 @@ public class Program
|
||||
app.UseSwagger();
|
||||
app.UseSwaggerUI();
|
||||
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
app.UseCors("Development");
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
app.UseCors("Production");
|
||||
|
||||
if (!app.Environment.IsDevelopment())
|
||||
{
|
||||
app.UseHttpsRedirection();
|
||||
}
|
||||
|
||||
|
||||
app.UseSession();
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
using AutoMapper;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using PlaylistShared.Api.Data;
|
||||
using PlaylistShared.Api.Entities;
|
||||
using PlaylistShared.Shared.Auth;
|
||||
using PlaylistShared.Shared.Enums;
|
||||
using PlaylistShared.Shared.Playlist;
|
||||
using PlaylistShared.Shared.Shared;
|
||||
@@ -11,13 +11,11 @@ namespace PlaylistShared.Api.Services;
|
||||
public class SharedPlaylistService
|
||||
{
|
||||
private readonly ApplicationDbContext _db;
|
||||
private readonly IMapper _mapper;
|
||||
private readonly TrackAdditionLogService _trackLogService;
|
||||
|
||||
public SharedPlaylistService(ApplicationDbContext db, IMapper mapper, TrackAdditionLogService trackLogService)
|
||||
public SharedPlaylistService(ApplicationDbContext db, TrackAdditionLogService trackLogService)
|
||||
{
|
||||
_db = db;
|
||||
_mapper = mapper;
|
||||
_trackLogService = trackLogService;
|
||||
}
|
||||
|
||||
@@ -27,6 +25,7 @@ public class SharedPlaylistService
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
CreatorUserId = creatorUserId,
|
||||
YandexPlaylistUuid = dto.YandexPlaylistUuid,
|
||||
YandexPlaylistKind = dto.YandexPlaylistKind,
|
||||
YandexPlaylistOwnerUid = dto.YandexPlaylistOwnerUid,
|
||||
Title = dto.Title,
|
||||
@@ -34,13 +33,14 @@ public class SharedPlaylistService
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
UpdatedAt = DateTime.UtcNow,
|
||||
ShareToken = GenerateToken(),
|
||||
PlayPermission = dto.PlayPermission,
|
||||
ViewPermission = dto.ViewPermission,
|
||||
AddPermission = dto.AddPermission,
|
||||
RemovePermission = dto.RemovePermission
|
||||
};
|
||||
_db.SharedPlaylists.Add(entity);
|
||||
await _db.SaveChangesAsync();
|
||||
return _mapper.Map<SharedPlaylistDto>(entity);
|
||||
return MapToDto(entity);
|
||||
}
|
||||
|
||||
public async Task<SharedPlaylistDto?> GetByTokenAsync(string token)
|
||||
@@ -48,7 +48,7 @@ public class SharedPlaylistService
|
||||
var entity = await _db.SharedPlaylists
|
||||
.Include(sp => sp.Creator)
|
||||
.FirstOrDefaultAsync(sp => sp.ShareToken == token && !sp.IsDeleted);
|
||||
return entity == null ? null : _mapper.Map<SharedPlaylistDto>(entity);
|
||||
return entity == null ? null : MapToDto(entity);
|
||||
}
|
||||
|
||||
public async Task<SharedPlaylist?> GetEntityByTokenAsync(string token)
|
||||
@@ -63,11 +63,12 @@ public class SharedPlaylistService
|
||||
var entity = await _db.SharedPlaylists.FindAsync(playlistId);
|
||||
if (entity == null) return null;
|
||||
entity.ViewPermission = dto.ViewPermission;
|
||||
entity.PlayPermission = dto.PlayPermission;
|
||||
entity.AddPermission = dto.AddPermission;
|
||||
entity.RemovePermission = dto.RemovePermission;
|
||||
entity.UpdatedAt = DateTime.UtcNow;
|
||||
await _db.SaveChangesAsync();
|
||||
return _mapper.Map<SharedPlaylistDto>(entity);
|
||||
return MapToDto(entity);
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteAsync(Guid playlistId)
|
||||
@@ -80,6 +81,18 @@ public class SharedPlaylistService
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> CanPlayAsync(SharedPlaylist playlist, Guid? currentUserId)
|
||||
{
|
||||
if (currentUserId == playlist.CreatorUserId) return true;
|
||||
return playlist.PlayPermission == ViewPermission.Everyone ||
|
||||
(playlist.PlayPermission == ViewPermission.AuthorizedOnly && currentUserId.HasValue);
|
||||
}
|
||||
|
||||
public async Task<bool> CanPlayEveryoneAsync(SharedPlaylist playlist)
|
||||
{
|
||||
return playlist.PlayPermission == ViewPermission.Everyone;
|
||||
}
|
||||
|
||||
public async Task<bool> CanViewAsync(SharedPlaylist playlist, Guid? currentUserId)
|
||||
{
|
||||
if (currentUserId == playlist.CreatorUserId) return true;
|
||||
@@ -129,4 +142,36 @@ public class SharedPlaylistService
|
||||
.Where(sp => sp.CreatorUserId == userId && !sp.IsDeleted)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
// Ручное маппинг сущности в DTO
|
||||
private SharedPlaylistDto MapToDto(SharedPlaylist entity)
|
||||
{
|
||||
return new SharedPlaylistDto
|
||||
{
|
||||
Id = entity.Id,
|
||||
CreatorUserId = entity.CreatorUserId,
|
||||
YandexPlaylistUuid = entity.YandexPlaylistUuid,
|
||||
YandexPlaylistKind = entity.YandexPlaylistKind,
|
||||
YandexPlaylistOwnerUid = entity.YandexPlaylistOwnerUid,
|
||||
Title = entity.Title,
|
||||
Description = entity.Description,
|
||||
CoverUrl = entity.CoverUrl,
|
||||
CreatedAt = entity.CreatedAt,
|
||||
UpdatedAt = entity.UpdatedAt,
|
||||
IsDeleted = entity.IsDeleted,
|
||||
ShareToken = entity.ShareToken,
|
||||
ViewPermission = entity.ViewPermission,
|
||||
PlayPermission = entity.PlayPermission,
|
||||
AddPermission = entity.AddPermission,
|
||||
RemovePermission = entity.RemovePermission,
|
||||
Creator = entity.Creator != null ? new ApplicationUserDto
|
||||
{
|
||||
Id = entity.Creator.Id,
|
||||
UserName = entity.Creator.UserName ?? string.Empty,
|
||||
Email = entity.Creator.Email,
|
||||
YandexId = entity.Creator.YandexId,
|
||||
DisplayName = entity.Creator.UserName
|
||||
} : null
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,12 @@
|
||||
"Issuer": "PlaylistShared.Api",
|
||||
"Audience": "PlaylistShared.Client"
|
||||
},
|
||||
"Cors": {
|
||||
"Origins": [
|
||||
"https://api.playlistshare.frigat.duckdns.org",
|
||||
"https://playlistshare.frigat.duckdns.org"
|
||||
]
|
||||
},
|
||||
"Yandex": {
|
||||
"ClientId": "0916685f8a3641ca8fc382dbccf77236",
|
||||
"ClientSecret": "f7398893cd814f8b84b85aeb2a0a6698"
|
||||
|
||||
@@ -59,12 +59,16 @@
|
||||
|
||||
[Inject] protected IJSRuntime JS { get; set; } = null!;
|
||||
[Inject] private TokenStorage TokenStorage { get; set; } = null!;
|
||||
[Inject] private PlayerStorage PlayerStorage { get; set; } = null!;
|
||||
[Inject] private AuthenticationStateProvider AuthProvider { get; set; } = null!;
|
||||
[Inject] private ISnackbar Snackbar { get; set; } = null!;
|
||||
|
||||
/// <summary>Требовать ли авторизацию для воспроизведения (по умолчанию true).</summary>
|
||||
[Parameter] public bool RequireAuth { get; set; } = true;
|
||||
|
||||
/// <summary>ID расшаренного плейлиста.</summary>
|
||||
[Parameter] public string SharedPlaylistId { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Событие при завершении трека.</summary>
|
||||
[Parameter] public EventCallback OnTrackEnded { get; set; }
|
||||
|
||||
@@ -73,6 +77,8 @@
|
||||
if (firstRender)
|
||||
{
|
||||
await EnsureAudioModuleAsync();
|
||||
await ChangeVolume(await PlayerStorage.GetVolumeAsync());
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,16 +145,16 @@
|
||||
var tokens = await TokenStorage.GetTokensAsync();
|
||||
var accessToken = tokens.token;
|
||||
|
||||
if (string.IsNullOrEmpty(accessToken))
|
||||
if (string.IsNullOrWhiteSpace(accessToken) && string.IsNullOrWhiteSpace(SharedPlaylistId))
|
||||
{
|
||||
Snackbar.Add("Токен авторизации не найден. Пожалуйста, войдите заново.", Severity.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
var streamUrl = new Uri(Http.BaseAddress, $"/api/audio/track/{trackId}").ToString();
|
||||
var streamUrl = new Uri(Http.BaseAddress!, $"/api/audio/track/{trackId}").ToString();
|
||||
|
||||
await EnsureAudioModuleAsync();
|
||||
await _audioElement!.InvokeVoidAsync("loadAndPlay", streamUrl, accessToken);
|
||||
await _audioElement!.InvokeVoidAsync("loadAndPlay", streamUrl, accessToken, SharedPlaylistId);
|
||||
_isPlaying = true;
|
||||
StartProgressTimer();
|
||||
StateHasChanged();
|
||||
@@ -223,6 +229,8 @@
|
||||
var volume = value / 100;
|
||||
await _audioElement.InvokeVoidAsync("setVolume", volume);
|
||||
_isMuted = false;
|
||||
_currentVolume = value;
|
||||
await PlayerStorage.SetVolumeAsync(value);
|
||||
StateHasChanged();
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
75
PlaylistShared.Pwa/Components/YandexTokenInstructions.razor
Normal file
75
PlaylistShared.Pwa/Components/YandexTokenInstructions.razor
Normal file
@@ -0,0 +1,75 @@
|
||||
@* Компонент с инструкцией по получению токена Яндекс.Музыки *@
|
||||
|
||||
<MudContainer Class="pa-4">
|
||||
<MudText Typo="Typo.body2" GutterBottom>
|
||||
Токен нужен для доступа к вашим плейлистам. Получите его один раз:
|
||||
</MudText>
|
||||
|
||||
<!-- Вертикальный список шагов -->
|
||||
<div class="instruction-steps">
|
||||
<div class="step-item">
|
||||
<div class="step-number">1</div>
|
||||
<div class="step-content">
|
||||
Перейдите по <MudLink Href="https://oauth.yandex.ru/authorize?response_type=token&client_id=23cabbbdc6cd418abb4b39c32c41195d" Target="_blank">ссылке</MudLink>
|
||||
</div>
|
||||
</div>
|
||||
<div class="step-item">
|
||||
<div class="step-number">2</div>
|
||||
<div class="step-content">
|
||||
Авторизуйтесь в Яндексе (если ещё не вошли)
|
||||
</div>
|
||||
</div>
|
||||
<div class="step-item">
|
||||
<div class="step-number">3</div>
|
||||
<div class="step-content">
|
||||
Нажмите «Разрешить»
|
||||
</div>
|
||||
</div>
|
||||
<div class="step-item">
|
||||
<div class="step-number">4</div>
|
||||
<div class="step-content">
|
||||
Скопируйте <strong>access_token</strong> из адресной строки после перенаправления
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<MudAlert Severity="Severity.Info" Class="mt-4">
|
||||
Пример: <code>https://music.yandex.ru/#access_token=ВАШ_ТОКЕН&...</code>
|
||||
</MudAlert>
|
||||
|
||||
<MudAlert Severity="Severity.Warning" Class="mt-2">
|
||||
Токен даёт доступ к вашим плейлистам. Никому его не сообщайте.
|
||||
</MudAlert>
|
||||
|
||||
<MudAlert Severity="Severity.Success" Class="mt-2" Icon="@Icons.Material.Filled.Security">
|
||||
Ваш токен сохраняется в зашифрованном виде и никому не передаётся.
|
||||
</MudAlert>
|
||||
</MudContainer>
|
||||
|
||||
<style>
|
||||
.instruction-steps {
|
||||
margin: 16px 0;
|
||||
}
|
||||
.step-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.step-number {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
background-color: var(--mud-palette-primary);
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: bold;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.step-content {
|
||||
flex: 1;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
</style>
|
||||
@@ -2,18 +2,23 @@
|
||||
|
||||
<AuthorizeView>
|
||||
<Authorized>
|
||||
<MudText Typo="Typo.body2" Class="d-inline mr-2">Здравствуйте, @context.User.Identity?.Name!</MudText>
|
||||
<MudButton Variant="Variant.Text" Color="Color.Inherit" OnClick="BeginLogOut">Выйти</MudButton>
|
||||
<MudMenu Label="@context.User.Identity?.Name" Variant="Variant.Text" Color="Color.Inherit" Class="user-menu">
|
||||
<MudMenuItem OnClick="GoToProfile">Профиль</MudMenuItem>
|
||||
<MudMenuItem OnClick="BeginLogOut">Выйти</MudMenuItem>
|
||||
</MudMenu>
|
||||
</Authorized>
|
||||
<NotAuthorized>
|
||||
<MudLink Href="/login" Color="Color.Inherit" Underline="Underline.Hover" Typo="Typo.body2">Вход</MudLink>
|
||||
<MudText Class="d-inline mx-1">|</MudText>
|
||||
<MudLink Href="/register" Color="Color.Inherit" Underline="Underline.Hover" Typo="Typo.body2">Регистрация</MudLink>
|
||||
</NotAuthorized>
|
||||
</AuthorizeView>
|
||||
|
||||
@code {
|
||||
public void BeginLogOut()
|
||||
private void GoToProfile()
|
||||
{
|
||||
Navigation.NavigateTo("/profile");
|
||||
}
|
||||
|
||||
private void BeginLogOut()
|
||||
{
|
||||
Navigation.NavigateTo("/logout");
|
||||
}
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
<MudNavLink Href="/" Match="NavLinkMatch.All" Icon="@Icons.Material.Filled.Home">Главная</MudNavLink>
|
||||
<AuthorizeView>
|
||||
<Authorized>
|
||||
<MudNavLink Href="/my-playlists" Icon="@Icons.Material.Filled.QueueMusic">Мои плейлисты</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.Star">Избранное</MudNavLink>
|
||||
</Authorized>
|
||||
</AuthorizeView>
|
||||
</MudNavMenu>
|
||||
110
PlaylistShared.Pwa/Pages/Favorites.razor
Normal file
110
PlaylistShared.Pwa/Pages/Favorites.razor
Normal file
@@ -0,0 +1,110 @@
|
||||
@page "/favorites"
|
||||
@using PlaylistShared.Shared.Shared
|
||||
@attribute [Authorize]
|
||||
@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>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd DataLabel="Название">
|
||||
<MudLink Href="@($"/shared/{context.ShareToken}")" Underline="Underline.Hover">
|
||||
@context.Title
|
||||
</MudLink>
|
||||
</MudTd>
|
||||
<MudTd DataLabel="Владелец">@context.Creator?.UserName</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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,91 +1,74 @@
|
||||
@page "/"
|
||||
@using PlaylistShared.Pwa.Services
|
||||
@inject NavigationManager Navigation
|
||||
@inject AuthenticationStateProvider AuthProvider
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.Large" Class="mt-8">
|
||||
<MudCard>
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudText Typo="Typo.h4" GutterBottom>🎵 Playlist share</MudText>
|
||||
<MudText Typo="Typo.body1">Делитесь плейлистами Яндекс.Музыки с друзьями и управляйте треками вместе!</MudText>
|
||||
<MudText Typo="Typo.body1">
|
||||
Делитесь плейлистами Яндекс.Музыки с друзьями и управляйте треками вместе!
|
||||
</MudText>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
|
||||
<MudCardContent>
|
||||
<MudText Typo="Typo.h6" GutterBottom>🚀 Как начать</MudText>
|
||||
<MudText Typo="Typo.body2" Class="mb-4">
|
||||
Playlist share — это веб-приложение, которое позволяет создавать совместные плейлисты,
|
||||
предоставлять доступ к ним по ссылке и слушать музыку прямо в браузере.
|
||||
Для работы требуется аккаунт Яндекс.Музыки (подписка не обязательна).
|
||||
</MudText>
|
||||
|
||||
<!-- Блок с требованием регистрации для расшаривания -->
|
||||
<MudAlert Severity="Severity.Info" Variant="Variant.Outlined" Class="my-4">
|
||||
<div style="display: flex; align-items: center; gap: 12px; flex-wrap: wrap;">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Share" Size="Size.Medium" />
|
||||
<div style="flex: 1;">
|
||||
<MudText Typo="Typo.body1" FontWeight="FontWeight.Bold">
|
||||
Чтобы расшаривать плейлисты, необходимо зарегистрироваться
|
||||
</MudText>
|
||||
<MudText Typo="Typo.body2">
|
||||
Создайте аккаунт или войдите в существующий — это займёт всего минуту.
|
||||
</MudText>
|
||||
</div>
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary" Href="/register">
|
||||
Зарегистрироваться
|
||||
</MudButton>
|
||||
<MudButton Variant="Variant.Outlined" Color="Color.Primary" Href="/login">
|
||||
Войти
|
||||
</MudButton>
|
||||
</div>
|
||||
</MudAlert>
|
||||
|
||||
<!-- Краткие преимущества -->
|
||||
<MudGrid>
|
||||
<MudItem xs="12" md="6">
|
||||
<MudPaper Class="pa-4" Elevation="0" Style="background-color: rgba(0,0,0,0.04); border-radius: 8px;">
|
||||
<MudText Typo="Typo.h6" GutterBottom>1️⃣ Регистрация и вход</MudText>
|
||||
<MudText Typo="Typo.body2" Class="mb-2">
|
||||
• Нажмите <MudLink Href="/register" Style="font-weight:bold;">«Регистрация»</MudLink> и создайте аккаунт.<br />
|
||||
• Или <MudLink Href="/login" Style="font-weight:bold;">войдите</MudLink> в систему, если уже зарегистрированы.
|
||||
</MudText>
|
||||
<MudItem xs="12" sm="4">
|
||||
<MudPaper Class="pa-3 text-center" Elevation="0" Style="background-color: rgba(0,0,0,0.04); border-radius: 8px;">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Link" Size="Size.Medium" Color="Color.Primary" />
|
||||
<MudText Typo="Typo.body2" Class="mt-2">Создавайте ссылки-приглашения</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="12" md="6">
|
||||
<MudPaper Class="pa-4" Elevation="0" Style="background-color: rgba(0,0,0,0.04); border-radius: 8px;">
|
||||
<MudText Typo="Typo.h6" GutterBottom>2️⃣ Получение токена Яндекс.Музыки</MudText>
|
||||
<MudText Typo="Typo.body2" Class="mb-2">
|
||||
Токен нужен для доступа к вашим плейлистам. Получите его один раз:
|
||||
</MudText>
|
||||
<ol style="margin-left: 1.2rem;">
|
||||
<li>Перейдите по <MudLink Href="https://oauth.yandex.ru/authorize?response_type=token&client_id=23cabbbdc6cd418abb4b39c32c41195d" Target="_blank">ссылке</MudLink></li>
|
||||
<li>Авторизуйтесь в Яндексе (если ещё не вошли)</li>
|
||||
<li>Нажмите «Разрешить»</li>
|
||||
<li>Скопируйте <strong>access_token</strong> из адресной строки после перенаправления</li>
|
||||
</ol>
|
||||
<MudAlert Severity="Severity.Info" Class="mt-2">
|
||||
Пример: <code>https://music.yandex.ru/#access_token=ВАШ_ТОКЕН&...</code>
|
||||
</MudAlert>
|
||||
<MudItem xs="12" sm="4">
|
||||
<MudPaper Class="pa-3 text-center" Elevation="0" Style="background-color: rgba(0,0,0,0.04); border-radius: 8px;">
|
||||
<MudIcon Icon="@Icons.Material.Filled.People" Size="Size.Medium" Color="Color.Primary" />
|
||||
<MudText Typo="Typo.body2" Class="mt-2">Совместное управление треками</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="12" md="6">
|
||||
<MudPaper Class="pa-4" Elevation="0" Style="background-color: rgba(0,0,0,0.04); border-radius: 8px;">
|
||||
<MudText Typo="Typo.h6" GutterBottom>3️⃣ Добавление токена в профиле</MudText>
|
||||
<MudText Typo="Typo.body2" Class="mb-2">
|
||||
• Перейдите в <MudLink Href="/profile" Style="font-weight:bold;">Профиль</MudLink><br />
|
||||
• Вставьте скопированный токен в поле «Токен Яндекс.Музыки»<br />
|
||||
• Нажмите «Сохранить»
|
||||
</MudText>
|
||||
<MudAlert Severity="Severity.Success" Class="mt-2">✅ Токен сохраняется в зашифрованном виде.</MudAlert>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="12" md="6">
|
||||
<MudPaper Class="pa-4" Elevation="0" Style="background-color: rgba(0,0,0,0.04); border-radius: 8px;">
|
||||
<MudText Typo="Typo.h6" GutterBottom>4️⃣ Расшаривание плейлиста</MudText>
|
||||
<MudText Typo="Typo.body2" Class="mb-2">
|
||||
• Откройте <MudLink Href="/my-playlists" Style="font-weight:bold;">Мои плейлисты</MudLink><br />
|
||||
• Нажмите «Поделиться» рядом с нужным плейлистом<br />
|
||||
• Скопируйте полученную ссылку и отправьте друзьям
|
||||
</MudText>
|
||||
<MudAlert Severity="Severity.Info" Class="mt-2">
|
||||
Вы можете настроить права на добавление/удаление треков для гостей.
|
||||
</MudAlert>
|
||||
<MudItem xs="12" sm="4">
|
||||
<MudPaper Class="pa-3 text-center" Elevation="0" Style="background-color: rgba(0,0,0,0.04); border-radius: 8px;">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Security" Size="Size.Medium" Color="Color.Primary" />
|
||||
<MudText Typo="Typo.body2" Class="mt-2">Гибкие настройки доступа</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
|
||||
<MudDivider Class="my-6" />
|
||||
<MudDivider Class="my-4" />
|
||||
|
||||
<MudText Typo="Typo.h6" GutterBottom>📌 Важно</MudText>
|
||||
<MudGrid>
|
||||
<MudItem xs="12" sm="6">
|
||||
<MudAlert Severity="Severity.Warning" Variant="Variant.Outlined">
|
||||
🔐 Токен даёт доступ к вашим плейлистам. Никому его не сообщайте.
|
||||
</MudAlert>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6">
|
||||
<MudAlert Severity="Severity.Info" Variant="Variant.Outlined">
|
||||
🎧 Для работы с плейлистами нужна активная подписка Яндекс.Плюс?<br />
|
||||
<MudText Typo="Typo.body2">Нет, достаточно обычного аккаунта.</MudText>
|
||||
</MudAlert>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
<MudText Typo="Typo.body2" Color="Color.Secondary">
|
||||
🔐 Все данные передаются по защищённому соединению, токены хранятся в зашифрованном виде.
|
||||
</MudText>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</MudContainer>
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
<!-- Локальная форма входа -->
|
||||
<MudTextField @bind-Value="_loginModel.Username" Label="Имя пользователя" Variant="Variant.Outlined" FullWidth="true" Class="mb-3" />
|
||||
<MudTextField @bind-Value="_loginModel.Password" Label="Пароль" Variant="Variant.Outlined" FullWidth="true" InputType="InputType.Password" />
|
||||
<MudTextField @bind-Value="_loginModel.Password" Label="Пароль" Variant="Variant.Outlined" FullWidth="true" InputType="InputType.Password" @onkeypress="@(async (e) => { if (e.Key == "Enter") await LocalLogin(); })" />
|
||||
|
||||
<MudButton Variant="Variant.Outlined" Color="Color.Secondary" OnClick="LocalLogin" FullWidth="true" Class="mt-4">
|
||||
Войти (локально)
|
||||
|
||||
@@ -13,17 +13,45 @@
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent>
|
||||
<MudText Typo="Typo.body2" Class="mb-4">Здесь вы можете указать токен доступа к Яндекс.Музыке.</MudText>
|
||||
<div style="display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 8px; margin-bottom: 16px;">
|
||||
<MudText Typo="Typo.body2">
|
||||
Здесь вы можете указать токен доступа к Яндекс.Музыке.
|
||||
</MudText>
|
||||
<MudIconButton Icon="@Icons.Material.Filled.HelpOutline"
|
||||
Color="Color.Info"
|
||||
OnClick="() => _instructionDrawerOpen = true"
|
||||
Title="Как получить токен?" />
|
||||
</div>
|
||||
|
||||
<MudTextField @bind-Value="_token" Label="Токен Яндекс.Музыки" Variant="Variant.Outlined" FullWidth="true" />
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="SaveToken" Class="mt-4" FullWidth="true">Сохранить токен</MudButton>
|
||||
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="SaveToken" Class="mt-4" FullWidth="true">
|
||||
Сохранить токен
|
||||
</MudButton>
|
||||
|
||||
<MudText Class="mt-4" Typo="Typo.body2">Статус: @_statusText</MudText>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</MudContainer>
|
||||
|
||||
<!-- Выдвижная панель с инструкцией -->
|
||||
<MudDrawer @bind-Open="_instructionDrawerOpen"
|
||||
Anchor="Anchor.Right"
|
||||
Variant="DrawerVariant.Temporary"
|
||||
Elevation="3"
|
||||
Width="500px"
|
||||
MiniWidth="0px">
|
||||
<MudDrawerHeader>
|
||||
<MudText Typo="Typo.h6">Как получить токен Яндекс.Музыки</MudText>
|
||||
</MudDrawerHeader>
|
||||
<MudDivider />
|
||||
<YandexTokenInstructions />
|
||||
</MudDrawer>
|
||||
|
||||
@code {
|
||||
private string _token = "";
|
||||
private string _statusText = "Загрузка...";
|
||||
private bool _instructionDrawerOpen = false;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
|
||||
@@ -27,10 +27,21 @@
|
||||
<div style="display: flex; gap: 16px; align-items: center;">
|
||||
@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>
|
||||
<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}")" Typo="Typo.h5" 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.Star : Icons.Material.Outlined.StarBorder)"
|
||||
Color="Color.Warning"
|
||||
OnClick="ToggleFavorite"
|
||||
Disabled="_favoriteLoading"
|
||||
Size="Size.Medium" />
|
||||
</div>
|
||||
<MudText Typo="Typo.body2" Color="Color.Secondary">Владелец: @_playlist.Creator?.UserName</MudText>
|
||||
</div>
|
||||
</div>
|
||||
@@ -50,6 +61,12 @@
|
||||
<MudSelectItem Value="ViewPermission.AuthorizedOnly">Только авторизованные</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="4">
|
||||
<MudSelect T="ViewPermission" Label="Воспроизведение" @bind-Value="_editPermissions.PlayPermission" Variant="Variant.Outlined" FullWidth="true">
|
||||
<MudSelectItem Value="ViewPermission.Everyone">Все</MudSelectItem>
|
||||
<MudSelectItem Value="ViewPermission.AuthorizedOnly">Только авторизованные</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="4">
|
||||
<MudSelect T="EditPermission" Label="Добавление треков" @bind-Value="_editPermissions.AddPermission" Variant="Variant.Outlined" FullWidth="true">
|
||||
<MudSelectItem Value="EditPermission.Everyone">Все</MudSelectItem>
|
||||
@@ -143,6 +160,8 @@
|
||||
<MudTd DataLabel="#" Style="font-weight: normal;">@context.Index</MudTd>
|
||||
<MudTd DataLabel="Обложка">
|
||||
@if (!string.IsNullOrEmpty(context.CoverUri))
|
||||
{
|
||||
@if (@_canPlay)
|
||||
{
|
||||
<TrackCoverWithPlay CoverUrl="@context.CoverUri"
|
||||
TrackId="@context.Id"
|
||||
@@ -150,6 +169,12 @@
|
||||
IsPlaying="@(_currentTrackId == context.Id && _isPlaying)"
|
||||
OnPlay="PlayTrack" />
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
<MudImage Src="@FormatCoverUrl(context.CoverUri, "50x50")" Height="50" Width="50" Class="rounded" Style="display: block;" />
|
||||
}
|
||||
}
|
||||
</MudTd>
|
||||
<MudTd DataLabel="Название">
|
||||
<MudLink Href="@($"https://music.yandex.ru/track/{context.Id}")" Target="_blank" Underline="Underline.Hover">
|
||||
@@ -174,7 +199,7 @@
|
||||
|
||||
<!-- Фиксированный плеер внизу -->
|
||||
<div class="fixed-player" style="display: @(_isPlayerVisible ? "block" : "none");">
|
||||
<AudioPlayer @ref="_audioPlayer" OnTrackEnded="OnTrackEnded" />
|
||||
<AudioPlayer @ref="_audioPlayer" OnTrackEnded="OnTrackEnded" RequireAuth="false" SharedPlaylistId="@Token"/>
|
||||
</div>
|
||||
</MudContainer>
|
||||
|
||||
@@ -191,12 +216,16 @@
|
||||
private bool _loading = true;
|
||||
private bool _isAuthenticated;
|
||||
private bool _isCreator;
|
||||
private bool _canPlay;
|
||||
private bool _canAdd;
|
||||
private bool _canRemove;
|
||||
private UpdatePermissionsDto _editPermissions = new();
|
||||
private bool _savingPermissions;
|
||||
private string? _currentUserId;
|
||||
|
||||
private bool _isFavorite = false;
|
||||
private bool _favoriteLoading = false;
|
||||
|
||||
private List<YandexTrackDisplay> _tracks = new();
|
||||
private bool _tracksLoading;
|
||||
|
||||
@@ -211,14 +240,78 @@
|
||||
await LoadPlaylist();
|
||||
}
|
||||
|
||||
private async Task LoadPlaylist()
|
||||
private async Task CheckFavoriteStatus()
|
||||
{
|
||||
if (!_isAuthenticated || _playlist == null) return;
|
||||
try
|
||||
{
|
||||
var response = await Http.GetFromJsonAsync<ApiResponse<SharedPlaylistDto>>($"/api/sharedplaylist/{Token}");
|
||||
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()
|
||||
{
|
||||
if (_playlist is null)
|
||||
{
|
||||
_isCreator = false;
|
||||
_canAdd = false;
|
||||
_canRemove = false;
|
||||
_canPlay = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
_playlist = response.Data;
|
||||
_isCreator = _playlist.CreatorUserId.ToString() == _currentUserId;
|
||||
|
||||
_canAdd = _isCreator
|
||||
@@ -229,17 +322,36 @@
|
||||
|| _playlist.RemovePermission == EditPermission.Everyone
|
||||
|| (_playlist.RemovePermission == EditPermission.AuthorizedOnly && _isAuthenticated);
|
||||
|
||||
_canPlay = _isCreator
|
||||
|| _playlist.PlayPermission == ViewPermission.Everyone
|
||||
|| (_playlist.PlayPermission == ViewPermission.AuthorizedOnly && _isAuthenticated);
|
||||
|
||||
if (_isCreator && _isAuthenticated)
|
||||
{
|
||||
_editPermissions = new UpdatePermissionsDto
|
||||
{
|
||||
ViewPermission = _playlist.ViewPermission,
|
||||
AddPermission = _playlist.AddPermission,
|
||||
RemovePermission = _playlist.RemovePermission
|
||||
RemovePermission = _playlist.RemovePermission,
|
||||
PlayPermission = _playlist.PlayPermission,
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadPlaylist()
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = await Http.GetFromJsonAsync<ApiResponse<SharedPlaylistDto>>($"/api/sharedplaylist/{Token}");
|
||||
if (response?.Success == true)
|
||||
{
|
||||
_playlist = response.Data;
|
||||
|
||||
await ConfigurePermissions();
|
||||
await LoadTracks();
|
||||
await CheckFavoriteStatus();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -371,11 +483,8 @@
|
||||
if (result?.Success == true)
|
||||
{
|
||||
_playlist = result.Data;
|
||||
Snackbar.Add("Права доступа обновлены", Severity.Success);
|
||||
_canAdd = _isCreator || _playlist.AddPermission == EditPermission.Everyone ||
|
||||
(_playlist.AddPermission == EditPermission.AuthorizedOnly && _isAuthenticated);
|
||||
_canRemove = _isCreator || _playlist.RemovePermission == EditPermission.Everyone ||
|
||||
(_playlist.RemovePermission == EditPermission.AuthorizedOnly && _isAuthenticated);
|
||||
|
||||
await ConfigurePermissions();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -21,6 +21,7 @@ internal class Program
|
||||
});
|
||||
|
||||
builder.Services.AddScoped<TokenStorage>();
|
||||
builder.Services.AddScoped<PlayerStorage>();
|
||||
builder.Services.AddScoped<AuthStateProvider>();
|
||||
builder.Services.AddScoped<AuthenticationStateProvider>(sp => sp.GetRequiredService<AuthStateProvider>());
|
||||
builder.Services.AddScoped<ApiClient>();
|
||||
|
||||
30
PlaylistShared.Pwa/Services/PlayerStorage.cs
Normal file
30
PlaylistShared.Pwa/Services/PlayerStorage.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using Microsoft.JSInterop;
|
||||
|
||||
namespace PlaylistShared.Pwa.Services;
|
||||
|
||||
public class PlayerStorage
|
||||
{
|
||||
private readonly IJSRuntime _js;
|
||||
private const string VolumeKey = "audio_player_volume";
|
||||
|
||||
public PlayerStorage(IJSRuntime js) => _js = js;
|
||||
|
||||
public async Task SetVolumeAsync(double volume)
|
||||
{
|
||||
await _js.InvokeVoidAsync("localStorage.setItem", VolumeKey, volume);
|
||||
}
|
||||
|
||||
public async Task<double> GetVolumeAsync()
|
||||
{
|
||||
var volume = await _js.InvokeAsync<string>("localStorage.getItem", VolumeKey);
|
||||
|
||||
if (double.TryParse(volume, out var result))
|
||||
{
|
||||
result = Math.Clamp(result, 0, 100);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -16,74 +16,6 @@
|
||||
<link rel="apple-touch-icon" sizes="512x512" href="icon-512.png" />
|
||||
<link rel="apple-touch-icon" sizes="192x192" href="icon-192.png" />
|
||||
<script type="importmap"></script>
|
||||
<style>
|
||||
html, body {
|
||||
background-color: #1a1a27 !important;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* Кастомный спиннер в стиле MudBlazor (тёмная тема) */
|
||||
.loading-progress {
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.loading-progress circle {
|
||||
fill: none;
|
||||
stroke: #2a2833;
|
||||
stroke-width: 4;
|
||||
transform-origin: 50% 50%;
|
||||
animation: spin 1.5s linear infinite;
|
||||
}
|
||||
|
||||
.loading-progress circle:last-child {
|
||||
stroke: #7e6fff;
|
||||
stroke-dasharray: 126;
|
||||
stroke-dashoffset: 126;
|
||||
animation: dash 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes dash {
|
||||
0% {
|
||||
stroke-dashoffset: 126;
|
||||
}
|
||||
|
||||
50% {
|
||||
stroke-dashoffset: 63;
|
||||
transform: rotate(135deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
stroke-dashoffset: 126;
|
||||
transform: rotate(450deg);
|
||||
}
|
||||
}
|
||||
|
||||
.loading-progress-text {
|
||||
text-align: center;
|
||||
margin-top: 16px;
|
||||
color: #b2b0bf;
|
||||
font-family: 'Roboto', sans-serif;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* Убираем белые вспышки */
|
||||
#app {
|
||||
background-color: #1a1a27;
|
||||
min-height: 100vh;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
@@ -10,9 +10,10 @@
|
||||
return isNaN(num) ? 0 : num;
|
||||
};
|
||||
|
||||
const loadAndPlay = (src, token) => {
|
||||
const loadAndPlay = (src, token, sharedPlaylistId) => {
|
||||
const url = new URL(src, window.location.href);
|
||||
if (token) url.searchParams.set('access_token', token);
|
||||
if (sharedPlaylistId) url.searchParams.set('shared_id', sharedPlaylistId);
|
||||
audio.src = url.toString();
|
||||
audio.load();
|
||||
durationReady = false;
|
||||
|
||||
@@ -6,6 +6,10 @@ namespace PlaylistShared.Shared.Playlist;
|
||||
/// <summary>Запрос на создание нового шеринг-плейлиста.</summary>
|
||||
public class SharePlaylistDto
|
||||
{
|
||||
/// <summary>Идентификатор плейлиста в Яндекс.Музыке (guid).</summary>
|
||||
[JsonPropertyName("yandexPlaylistId")]
|
||||
public string YandexPlaylistUuid { get; set; } = null!;
|
||||
|
||||
/// <summary>Идентификатор плейлиста в Яндекс.Музыке (kind).</summary>
|
||||
[JsonPropertyName("yandexPlaylistKind")]
|
||||
public string YandexPlaylistKind { get; set; } = null!;
|
||||
@@ -38,6 +42,10 @@ public class SharePlaylistDto
|
||||
[JsonPropertyName("viewPermission")]
|
||||
public ViewPermission ViewPermission { get; set; }
|
||||
|
||||
/// <summary>Права на воспроизведение.</summary>
|
||||
[JsonPropertyName("playPermission")]
|
||||
public ViewPermission PlayPermission { get; set; }
|
||||
|
||||
/// <summary>Права на добавление треков.</summary>
|
||||
[JsonPropertyName("addPermission")]
|
||||
public EditPermission AddPermission { get; set; }
|
||||
|
||||
@@ -15,6 +15,10 @@ public class SharedPlaylistDto
|
||||
[JsonPropertyName("creatorUserId")]
|
||||
public Guid CreatorUserId { get; set; }
|
||||
|
||||
/// <summary>Uuid на яндекс плейлист</summary>
|
||||
[JsonPropertyName("yandexPlaylistUuid")]
|
||||
public string? YandexPlaylistUuid { get; set; }
|
||||
|
||||
/// <summary>Идентификатор плейлиста в Яндекс.Музыке (kind).</summary>
|
||||
[JsonPropertyName("yandexPlaylistKind")]
|
||||
public string YandexPlaylistKind { get; set; } = null!;
|
||||
@@ -55,6 +59,10 @@ public class SharedPlaylistDto
|
||||
[JsonPropertyName("viewPermission")]
|
||||
public ViewPermission ViewPermission { get; set; }
|
||||
|
||||
/// <summary>Права на воспроизведение.</summary>
|
||||
[JsonPropertyName("playPermission")]
|
||||
public ViewPermission PlayPermission { get; set; }
|
||||
|
||||
/// <summary>Права на добавление треков.</summary>
|
||||
[JsonPropertyName("addPermission")]
|
||||
public EditPermission AddPermission { get; set; }
|
||||
|
||||
@@ -6,15 +6,19 @@ namespace PlaylistShared.Shared.Shared;
|
||||
/// <summary>Запрос на обновление прав доступа шеринг-плейлиста.</summary>
|
||||
public class UpdatePermissionsDto
|
||||
{
|
||||
/// <summary>Новые права на просмотр.</summary>
|
||||
/// <summary>Права на просмотр.</summary>
|
||||
[JsonPropertyName("viewPermission")]
|
||||
public ViewPermission ViewPermission { get; set; }
|
||||
|
||||
/// <summary>Новые права на добавление треков.</summary>
|
||||
/// <summary>Права на воспроизведение треков.</summary>
|
||||
[JsonPropertyName("playPermission")]
|
||||
public ViewPermission PlayPermission { get; set; }
|
||||
|
||||
/// <summary>Права на добавление треков.</summary>
|
||||
[JsonPropertyName("addPermission")]
|
||||
public EditPermission AddPermission { get; set; }
|
||||
|
||||
/// <summary>Новые права на удаление треков.</summary>
|
||||
/// <summary>Права на удаление треков.</summary>
|
||||
[JsonPropertyName("removePermission")]
|
||||
public EditPermission RemovePermission { get; set; }
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
services:
|
||||
playlistshared.api:
|
||||
environment:
|
||||
- ASPNETCORE_ENVIRONMENT=Development
|
||||
- ASPNETCORE_HTTP_PORTS=8080
|
||||
- ASPNETCORE_HTTPS_PORTS=8081
|
||||
ports:
|
||||
|
||||
Reference in New Issue
Block a user