Добавление треков
This commit is contained in:
@@ -14,79 +14,20 @@ namespace PlaylistShared.Api.Controllers;
|
|||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("api/[controller]")]
|
[Route("api/[controller]")]
|
||||||
[Authorize]
|
[Authorize]
|
||||||
public class PlaylistController : ControllerBase
|
public class PlaylistsController : ControllerBase
|
||||||
{
|
{
|
||||||
private readonly UserManager<ApplicationUser> _userManager;
|
private readonly UserManager<ApplicationUser> _userManager;
|
||||||
private readonly SharedPlaylistService _sharedService;
|
private readonly SharedPlaylistService _sharedService;
|
||||||
private readonly YandexMusicService _yandexService;
|
private readonly YandexMusicService _yandexService;
|
||||||
private readonly TrackAdditionLogService _trackLogService;
|
|
||||||
|
|
||||||
public PlaylistController(
|
public PlaylistsController(
|
||||||
UserManager<ApplicationUser> userManager,
|
UserManager<ApplicationUser> userManager,
|
||||||
SharedPlaylistService sharedService,
|
SharedPlaylistService sharedService,
|
||||||
YandexMusicService yandexService,
|
YandexMusicService yandexService)
|
||||||
TrackAdditionLogService trackLogService)
|
|
||||||
{
|
{
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
_sharedService = sharedService;
|
_sharedService = sharedService;
|
||||||
_yandexService = yandexService;
|
_yandexService = yandexService;
|
||||||
_trackLogService = trackLogService;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost("add-tracks")]
|
|
||||||
public async Task<ActionResult<ApiResponse<object>>> AddTracks([FromBody] AddTrackRequest request)
|
|
||||||
{
|
|
||||||
var currentUserId = User.GetUserId();
|
|
||||||
var playlist = await _sharedService.GetEntityByTokenAsync(request.SharedPlaylistToken);
|
|
||||||
if (playlist == null)
|
|
||||||
return NotFound(ApiResponse<object>.Fail(new ErrorResponse { StatusCode = 404, Message = "Плейлист не найден" }));
|
|
||||||
|
|
||||||
if (!await _sharedService.CanAddTrackAsync(playlist, currentUserId))
|
|
||||||
return StatusCode(403, ApiResponse<object>.Fail(new ErrorResponse { StatusCode = 403, Message = "Недостаточно прав для добавления треков" }));
|
|
||||||
|
|
||||||
var creator = await _userManager.FindByIdAsync(playlist.CreatorUserId.ToString());
|
|
||||||
if (creator == null)
|
|
||||||
return StatusCode(500, ApiResponse<object>.Fail(new ErrorResponse { StatusCode = 500, Message = "Владелец плейлиста не найден" }));
|
|
||||||
|
|
||||||
var updatedPlaylist = await _yandexService.AddTracksAsync(creator, playlist.YandexPlaylistOwnerUid, playlist.YandexPlaylistKind, request.TrackIds);
|
|
||||||
if (updatedPlaylist == null)
|
|
||||||
return StatusCode(500, ApiResponse<object>.Fail(new ErrorResponse { StatusCode = 500, Message = "Ошибка при добавлении треков в Яндекс.Музыку" }));
|
|
||||||
|
|
||||||
// Логируем добавления для права AddedByUserOnly
|
|
||||||
foreach (var trackId in request.TrackIds)
|
|
||||||
await _trackLogService.LogAdditionAsync(playlist.Id, trackId, currentUserId);
|
|
||||||
|
|
||||||
return Ok(ApiResponse<object>.Ok(new { message = "Треки успешно добавлены" }));
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost("remove-tracks")]
|
|
||||||
public async Task<ActionResult<ApiResponse<object>>> RemoveTracks([FromBody] AddTrackRequest request)
|
|
||||||
{
|
|
||||||
var currentUserId = User.GetUserId();
|
|
||||||
var playlist = await _sharedService.GetEntityByTokenAsync(request.SharedPlaylistToken);
|
|
||||||
if (playlist == null)
|
|
||||||
return NotFound(ApiResponse<object>.Fail(new ErrorResponse { StatusCode = 404, Message = "Плейлист не найден" }));
|
|
||||||
|
|
||||||
// Проверяем права на удаление каждого трека
|
|
||||||
foreach (var trackId in request.TrackIds)
|
|
||||||
{
|
|
||||||
if (!await _sharedService.CanRemoveTrackAsync(playlist, currentUserId, trackId))
|
|
||||||
return StatusCode(403, ApiResponse<object>.Fail(new ErrorResponse { StatusCode = 403, Message = $"Недостаточно прав для удаления трека {trackId}" }));
|
|
||||||
}
|
|
||||||
|
|
||||||
var creator = await _userManager.FindByIdAsync(playlist.CreatorUserId.ToString());
|
|
||||||
if (creator == null)
|
|
||||||
return StatusCode(500, ApiResponse<object>.Fail(new ErrorResponse { StatusCode = 500, Message = "Владелец плейлиста не найден" }));
|
|
||||||
|
|
||||||
var updatedPlaylist = await _yandexService.RemoveTracksAsync(creator, playlist.YandexPlaylistOwnerUid, playlist.YandexPlaylistKind, request.TrackIds);
|
|
||||||
if (updatedPlaylist == null)
|
|
||||||
return StatusCode(500, ApiResponse<object>.Fail(new ErrorResponse { StatusCode = 500, Message = "Ошибка при удалении треков из Яндекс.Музыки" }));
|
|
||||||
|
|
||||||
// Удаляем логи добавления для этих треков
|
|
||||||
foreach (var trackId in request.TrackIds)
|
|
||||||
await _trackLogService.RemoveLogsForTrackAsync(playlist.Id, trackId);
|
|
||||||
|
|
||||||
return Ok(ApiResponse<object>.Ok(new { message = "Треки успешно удалены" }));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("info/{ownerUid}/{kind}")]
|
[HttpGet("info/{ownerUid}/{kind}")]
|
||||||
@@ -108,7 +49,7 @@ public class PlaylistController : ControllerBase
|
|||||||
return Ok(ApiResponse<object>.Ok(yandexPlaylist));
|
return Ok(ApiResponse<object>.Ok(yandexPlaylist));
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("my")]
|
[HttpGet]
|
||||||
public async Task<ActionResult<ApiResponse<List<YandexPlaylistInfo>>>> GetMyPlaylists()
|
public async Task<ActionResult<ApiResponse<List<YandexPlaylistInfo>>>> GetMyPlaylists()
|
||||||
{
|
{
|
||||||
var userId = User.GetUserId();
|
var userId = User.GetUserId();
|
||||||
@@ -1,11 +1,12 @@
|
|||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using PlaylistShared.Api.Entities;
|
||||||
using PlaylistShared.Api.Extensions;
|
using PlaylistShared.Api.Extensions;
|
||||||
using PlaylistShared.Api.Services;
|
using PlaylistShared.Api.Services;
|
||||||
using PlaylistShared.Shared.DTO;
|
using PlaylistShared.Shared.DTO;
|
||||||
using PlaylistShared.Shared.Models;
|
using PlaylistShared.Shared.Models;
|
||||||
|
using YandexMusic.API.Models.Playlist;
|
||||||
namespace PlaylistShared.Api.Controllers;
|
|
||||||
|
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("api/[controller]")]
|
[Route("api/[controller]")]
|
||||||
@@ -13,25 +14,22 @@ public class SharedPlaylistController : ControllerBase
|
|||||||
{
|
{
|
||||||
private readonly SharedPlaylistService _sharedService;
|
private readonly SharedPlaylistService _sharedService;
|
||||||
private readonly YandexMusicService _yandexService;
|
private readonly YandexMusicService _yandexService;
|
||||||
|
private readonly UserManager<ApplicationUser> _userManager;
|
||||||
|
private readonly TrackAdditionLogService _trackLogService;
|
||||||
|
|
||||||
public SharedPlaylistController(SharedPlaylistService sharedService, YandexMusicService yandexService)
|
public SharedPlaylistController(
|
||||||
|
SharedPlaylistService sharedService,
|
||||||
|
YandexMusicService yandexService,
|
||||||
|
UserManager<ApplicationUser> userManager,
|
||||||
|
TrackAdditionLogService trackLogService)
|
||||||
{
|
{
|
||||||
_sharedService = sharedService;
|
_sharedService = sharedService;
|
||||||
_yandexService = yandexService;
|
_yandexService = yandexService;
|
||||||
|
_userManager = userManager;
|
||||||
|
_trackLogService = trackLogService;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost]
|
// GET /api/sharedplaylist/{token}
|
||||||
[Authorize]
|
|
||||||
public async Task<ActionResult<ApiResponse<SharedPlaylistDto>>> Create([FromBody] SharePlaylistDto dto)
|
|
||||||
{
|
|
||||||
var userId = User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value;
|
|
||||||
if (string.IsNullOrEmpty(userId) || !Guid.TryParse(userId, out var guid))
|
|
||||||
return Unauthorized();
|
|
||||||
|
|
||||||
var result = await _sharedService.CreateAsync(guid, dto);
|
|
||||||
return Ok(ApiResponse<SharedPlaylistDto>.Ok(result));
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("{token}")]
|
[HttpGet("{token}")]
|
||||||
public async Task<ActionResult<ApiResponse<SharedPlaylistDto>>> GetByToken(string token)
|
public async Task<ActionResult<ApiResponse<SharedPlaylistDto>>> GetByToken(string token)
|
||||||
{
|
{
|
||||||
@@ -50,6 +48,31 @@ public class SharedPlaylistController : ControllerBase
|
|||||||
return Ok(ApiResponse<SharedPlaylistDto>.Ok(playlist));
|
return Ok(ApiResponse<SharedPlaylistDto>.Ok(playlist));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GET /api/sharedplaylist/{token}/tracks
|
||||||
|
[HttpGet("{token}/tracks")]
|
||||||
|
public async Task<ActionResult<ApiResponse<YandexPlaylistData>>> GetTracks(string token)
|
||||||
|
{
|
||||||
|
var currentUserId = User.GetUserIdOrNull();
|
||||||
|
var playlist = await _sharedService.GetEntityByTokenAsync(token);
|
||||||
|
if (playlist == null)
|
||||||
|
return NotFound(ApiResponse<YandexPlaylistData>.Fail(new ErrorResponse { StatusCode = 404, Message = "Плейлист не найден" }));
|
||||||
|
|
||||||
|
if (!await _sharedService.CanViewAsync(playlist, currentUserId))
|
||||||
|
return Unauthorized();
|
||||||
|
|
||||||
|
var creator = await _userManager.FindByIdAsync(playlist.CreatorUserId.ToString());
|
||||||
|
if (creator == null)
|
||||||
|
return StatusCode(500, ApiResponse<YandexPlaylistData>.Fail(new ErrorResponse { StatusCode = 500, Message = "Владелец плейлиста не найден" }));
|
||||||
|
|
||||||
|
var yandexPlaylist = await _yandexService.GetPlaylistAsync(creator, playlist.YandexPlaylistOwnerUid, playlist.YandexPlaylistKind);
|
||||||
|
if (yandexPlaylist == null)
|
||||||
|
return NotFound(ApiResponse<YandexPlaylistData>.Fail(new ErrorResponse { StatusCode = 404, Message = "Плейлист не найден в Яндекс.Музыке" }));
|
||||||
|
|
||||||
|
var dto = MapToYandexPlaylistData(yandexPlaylist);
|
||||||
|
return Ok(ApiResponse<YandexPlaylistData>.Ok(dto));
|
||||||
|
}
|
||||||
|
|
||||||
|
// PUT /api/sharedplaylist/{token}/permissions
|
||||||
[HttpPut("{token}/permissions")]
|
[HttpPut("{token}/permissions")]
|
||||||
[Authorize]
|
[Authorize]
|
||||||
public async Task<ActionResult<ApiResponse<SharedPlaylistDto>>> UpdatePermissions(string token, [FromBody] UpdatePermissionsDto dto)
|
public async Task<ActionResult<ApiResponse<SharedPlaylistDto>>> UpdatePermissions(string token, [FromBody] UpdatePermissionsDto dto)
|
||||||
@@ -68,4 +91,90 @@ public class SharedPlaylistController : ControllerBase
|
|||||||
|
|
||||||
return Ok(ApiResponse<SharedPlaylistDto>.Ok(updated));
|
return Ok(ApiResponse<SharedPlaylistDto>.Ok(updated));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// POST /api/sharedplaylist/{token}/add-tracks
|
||||||
|
[HttpPost("{token}/add-tracks")]
|
||||||
|
public async Task<ActionResult<ApiResponse<object>>> AddTracks(string token, [FromBody] AddTracksRequest request)
|
||||||
|
{
|
||||||
|
var currentUserId = User.GetUserIdOrNull();
|
||||||
|
var playlist = await _sharedService.GetEntityByTokenAsync(token);
|
||||||
|
if (playlist == null)
|
||||||
|
return NotFound(ApiResponse<object>.Fail(new ErrorResponse { StatusCode = 404, Message = "Плейлист не найден" }));
|
||||||
|
|
||||||
|
if (!await _sharedService.CanAddTrackAsync(playlist, currentUserId))
|
||||||
|
return StatusCode(403, ApiResponse<object>.Fail(new ErrorResponse { StatusCode = 403, Message = "Недостаточно прав для добавления треков" }));
|
||||||
|
|
||||||
|
var creator = await _userManager.FindByIdAsync(playlist.CreatorUserId.ToString());
|
||||||
|
if (creator == null)
|
||||||
|
return StatusCode(500, ApiResponse<object>.Fail(new ErrorResponse { StatusCode = 500, Message = "Владелец плейлиста не найден" }));
|
||||||
|
|
||||||
|
var updatedPlaylist = await _yandexService.AddTracksAsync(creator, playlist.YandexPlaylistOwnerUid, playlist.YandexPlaylistKind, request.TrackIds);
|
||||||
|
if (updatedPlaylist == null)
|
||||||
|
return StatusCode(500, ApiResponse<object>.Fail(new ErrorResponse { StatusCode = 500, Message = "Ошибка при добавлении треков" }));
|
||||||
|
|
||||||
|
return Ok(ApiResponse<object>.Ok(new { message = "Треки добавлены" }));
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST /api/sharedplaylist/{token}/remove-tracks
|
||||||
|
[HttpPost("{token}/remove-tracks")]
|
||||||
|
public async Task<ActionResult<ApiResponse<object>>> RemoveTracks(string token, [FromBody] RemoveTracksRequest request)
|
||||||
|
{
|
||||||
|
var currentUserId = User.GetUserIdOrNull();
|
||||||
|
var playlist = await _sharedService.GetEntityByTokenAsync(token);
|
||||||
|
if (playlist == null)
|
||||||
|
return NotFound(ApiResponse<object>.Fail(new ErrorResponse { StatusCode = 404, Message = "Плейлист не найден" }));
|
||||||
|
|
||||||
|
foreach (var trackId in request.TrackIds)
|
||||||
|
{
|
||||||
|
if (!await _sharedService.CanRemoveTrackAsync(playlist, currentUserId, trackId))
|
||||||
|
return StatusCode(403, ApiResponse<object>.Fail(new ErrorResponse { StatusCode = 403, Message = $"Недостаточно прав для удаления трека {trackId}" }));
|
||||||
|
}
|
||||||
|
|
||||||
|
var creator = await _userManager.FindByIdAsync(playlist.CreatorUserId.ToString());
|
||||||
|
if (creator == null)
|
||||||
|
return StatusCode(500, ApiResponse<object>.Fail(new ErrorResponse { StatusCode = 500, Message = "Владелец плейлиста не найден" }));
|
||||||
|
|
||||||
|
var updatedPlaylist = await _yandexService.RemoveTracksAsync(creator, playlist.YandexPlaylistOwnerUid, playlist.YandexPlaylistKind, request.TrackIds);
|
||||||
|
if (updatedPlaylist == null)
|
||||||
|
return StatusCode(500, ApiResponse<object>.Fail(new ErrorResponse { StatusCode = 500, Message = "Ошибка при удалении треков" }));
|
||||||
|
|
||||||
|
foreach (var trackId in request.TrackIds)
|
||||||
|
await _trackLogService.RemoveLogsForTrackAsync(playlist.Id, trackId);
|
||||||
|
|
||||||
|
return Ok(ApiResponse<object>.Ok(new { message = "Треки удалены" }));
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST /api/sharedplaylist/{token}/add-track-by-link
|
||||||
|
[HttpPost("{token}/add-track-by-link")]
|
||||||
|
public async Task<ActionResult<ApiResponse<object>>> AddTrackByLink(string token, [FromBody] AddTrackByLinkRequest request)
|
||||||
|
{
|
||||||
|
var trackId = ExtractTrackIdFromLink(request.Link);
|
||||||
|
if (string.IsNullOrEmpty(trackId))
|
||||||
|
return BadRequest(ApiResponse<object>.Fail(new ErrorResponse { StatusCode = 400, Message = "Неверный формат ссылки" }));
|
||||||
|
|
||||||
|
return await AddTracks(token, new AddTracksRequest { TrackIds = new List<string> { trackId } });
|
||||||
|
}
|
||||||
|
|
||||||
|
private string? ExtractTrackIdFromLink(string link)
|
||||||
|
{
|
||||||
|
var match = System.Text.RegularExpressions.Regex.Match(link, @"/track/(\d+)");
|
||||||
|
return match.Success ? match.Groups[1].Value : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private YandexPlaylistData MapToYandexPlaylistData(YPlaylist playlist)
|
||||||
|
{
|
||||||
|
return new YandexPlaylistData
|
||||||
|
{
|
||||||
|
Title = playlist.Title ?? "",
|
||||||
|
Description = playlist.Description ?? "",
|
||||||
|
Tracks = playlist.Tracks?.Select(t => new YandexTrack
|
||||||
|
{
|
||||||
|
Id = t.Track?.Id ?? "",
|
||||||
|
Title = t.Track?.Title ?? "",
|
||||||
|
Artists = t.Track?.Artists?.Select(a => a.Name).ToList() ?? new List<string>(),
|
||||||
|
DurationMs = (int)(t.Track?.DurationMs ?? 0),
|
||||||
|
CoverUri = t.Track?.CoverUri ?? ""
|
||||||
|
}).ToList() ?? new List<YandexTrack>()
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="10.1.7" />
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="10.1.7" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore.Swagger" Version="10.1.7" />
|
<PackageReference Include="Swashbuckle.AspNetCore.Swagger" Version="10.1.7" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="10.1.7" />
|
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="10.1.7" />
|
||||||
<PackageReference Include="YandexMusic" Version="0.0.4" />
|
<PackageReference Include="YandexMusic" Version="0.0.5" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -8,12 +8,12 @@
|
|||||||
<MudLayout>
|
<MudLayout>
|
||||||
<MudAppBar Elevation="1">
|
<MudAppBar Elevation="1">
|
||||||
<MudIconButton Icon="@Icons.Material.Filled.Menu" Color="Color.Inherit" Edge="Edge.Start" OnClick="@DrawerToggle" />
|
<MudIconButton Icon="@Icons.Material.Filled.Menu" Color="Color.Inherit" Edge="Edge.Start" OnClick="@DrawerToggle" />
|
||||||
<MudText Typo="Typo.h6" Class="ml-2">Application</MudText>
|
<MudText Typo="Typo.h6" Class="ml-2">Playlist share</MudText>
|
||||||
<MudSpacer />
|
<MudSpacer />
|
||||||
<LoginDisplay />
|
<LoginDisplay />
|
||||||
<MudIconButton Icon="@(DarkLightModeButtonIcon)" Color="Color.Inherit" OnClick="@DarkModeToggle" Class="ml-2" />
|
<MudIconButton Icon="@(DarkLightModeButtonIcon)" Color="Color.Inherit" OnClick="@DarkModeToggle" Class="ml-2" />
|
||||||
<MudLink Href="https://learn.microsoft.com/aspnet/core/" Target="_blank" Color="Color.Inherit" Underline="Underline.None" Class="ml-4">
|
<MudLink Href="https://git.frigat.duckdns.org/FrigaT/PlaylistShared" Target="_blank" Color="Color.Inherit" Underline="Underline.None" Class="ml-4">
|
||||||
About
|
<MudIcon Icon="@Icons.Custom.Brands.GitHub" Size="Size.Small" Class="mr-1" /> Git
|
||||||
</MudLink>
|
</MudLink>
|
||||||
</MudAppBar>
|
</MudAppBar>
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,6 @@
|
|||||||
<Authorized>
|
<Authorized>
|
||||||
<MudNavLink Href="/my-playlists" Icon="@Icons.Material.Filled.QueueMusic">Мои плейлисты</MudNavLink>
|
<MudNavLink Href="/my-playlists" Icon="@Icons.Material.Filled.QueueMusic">Мои плейлисты</MudNavLink>
|
||||||
<MudNavLink Href="/profile" Icon="@Icons.Material.Filled.Person">Профиль</MudNavLink>
|
<MudNavLink Href="/profile" Icon="@Icons.Material.Filled.Person">Профиль</MudNavLink>
|
||||||
<MudNavLink Href="/create" Icon="@Icons.Material.Filled.Add">Создать плейлист</MudNavLink>
|
|
||||||
<MudNavLink Href="/my" Icon="@Icons.Material.Filled.List">Мои ссылки</MudNavLink>
|
|
||||||
</Authorized>
|
</Authorized>
|
||||||
</AuthorizeView>
|
</AuthorizeView>
|
||||||
</MudNavMenu>
|
</MudNavMenu>
|
||||||
@@ -1,18 +1,91 @@
|
|||||||
@page "/"
|
@page "/"
|
||||||
|
@using PlaylistShared.Pwa.Services
|
||||||
|
@inject NavigationManager Navigation
|
||||||
|
|
||||||
<PageTitle>Home</PageTitle>
|
<MudContainer MaxWidth="MaxWidth.Large" Class="mt-8">
|
||||||
|
<MudCard>
|
||||||
|
<MudCardHeader>
|
||||||
|
<CardHeaderContent>
|
||||||
|
<MudText Typo="Typo.h4" GutterBottom>🎵 PlaylistShared</MudText>
|
||||||
|
<MudText Typo="Typo.body1">Делитесь плейлистами Яндекс.Музыки с друзьями и управляйте треками вместе!</MudText>
|
||||||
|
</CardHeaderContent>
|
||||||
|
</MudCardHeader>
|
||||||
|
|
||||||
<MudText Typo="Typo.h3" GutterBottom="true">Hello, world!</MudText>
|
<MudCardContent>
|
||||||
<MudText Class="mb-8">Welcome to your new app, powered by MudBlazor and the .NET 10 Template!</MudText>
|
<MudText Typo="Typo.h6" GutterBottom>🚀 Как начать</MudText>
|
||||||
|
|
||||||
<MudAlert Severity="Severity.Warning" Variant="Variant.Outlined" Dense="true" Class="mb-6">
|
<MudGrid>
|
||||||
Before authentication will function correctly, you must configure your provider details in <code>Program.cs</code>.
|
<MudItem xs="12" md="6">
|
||||||
</MudAlert>
|
<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>
|
||||||
|
</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>
|
||||||
|
</MudPaper>
|
||||||
|
</MudItem>
|
||||||
|
|
||||||
<MudAlert Severity="Severity.Normal" ContentAlignment="HorizontalAlignment.Start">
|
<MudItem xs="12" md="6">
|
||||||
You can find documentation and examples on our website here:
|
<MudPaper Class="pa-4" Elevation="0" Style="background-color: rgba(0,0,0,0.04); border-radius: 8px;">
|
||||||
<MudLink Href="https://mudblazor.com" Target="_blank" Typo="Typo.body2" Color="Color.Primary">
|
<MudText Typo="Typo.h6" GutterBottom>3️⃣ Добавление токена в профиле</MudText>
|
||||||
<b>www.mudblazor.com</b>
|
<MudText Typo="Typo.body2" Class="mb-2">
|
||||||
</MudLink>
|
• Перейдите в <MudLink Href="/profile" Style="font-weight:bold;">Профиль</MudLink><br />
|
||||||
</MudAlert>
|
• Вставьте скопированный токен в поле «Токен Яндекс.Музыки»<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>
|
||||||
|
</MudPaper>
|
||||||
|
</MudItem>
|
||||||
|
</MudGrid>
|
||||||
|
|
||||||
|
<MudDivider Class="my-6" />
|
||||||
|
|
||||||
|
<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>
|
||||||
|
</MudCardContent>
|
||||||
|
</MudCard>
|
||||||
|
</MudContainer>
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
|
|
||||||
<!-- Локальная форма входа -->
|
<!-- Локальная форма входа -->
|
||||||
<MudTextField @bind-Value="_loginModel.Username" Label="Имя пользователя" Variant="Variant.Outlined" FullWidth="true" Class="mb-3" />
|
<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" Type="InputType.Password" />
|
<MudTextField @bind-Value="_loginModel.Password" Label="Пароль" Variant="Variant.Outlined" FullWidth="true" InputType="InputType.Password" />
|
||||||
|
|
||||||
<MudButton Variant="Variant.Outlined" Color="Color.Secondary" OnClick="LocalLogin" FullWidth="true" Class="mt-4">
|
<MudButton Variant="Variant.Outlined" Color="Color.Secondary" OnClick="LocalLogin" FullWidth="true" Class="mt-4">
|
||||||
Войти (локально)
|
Войти (локально)
|
||||||
|
|||||||
@@ -83,7 +83,7 @@
|
|||||||
_loading = true;
|
_loading = true;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var response = await Http.GetFromJsonAsync<ApiResponse<List<YandexPlaylistInfo>>>("/api/playlist/my");
|
var response = await Http.GetFromJsonAsync<ApiResponse<List<YandexPlaylistInfo>>>("/api/playlists");
|
||||||
if (response?.Success == true)
|
if (response?.Success == true)
|
||||||
_playlists = response.Data;
|
_playlists = response.Data;
|
||||||
else
|
else
|
||||||
@@ -103,7 +103,7 @@
|
|||||||
private async Task SharePlaylist(YandexPlaylistInfo playlist)
|
private async Task SharePlaylist(YandexPlaylistInfo playlist)
|
||||||
{
|
{
|
||||||
var request = new SharePlaylistRequest { Kind = playlist.Kind, OwnerUid = playlist.OwnerUid };
|
var request = new SharePlaylistRequest { Kind = playlist.Kind, OwnerUid = playlist.OwnerUid };
|
||||||
var response = await Http.PostAsJsonAsync("/api/playlist/share", request);
|
var response = await Http.PostAsJsonAsync("/api/playlists/share", request);
|
||||||
if (response.IsSuccessStatusCode)
|
if (response.IsSuccessStatusCode)
|
||||||
{
|
{
|
||||||
Snackbar.Add("Плейлист расшарен", Severity.Success);
|
Snackbar.Add("Плейлист расшарен", Severity.Success);
|
||||||
|
|||||||
@@ -11,8 +11,8 @@
|
|||||||
|
|
||||||
<MudTextField @bind-Value="_model.Username" Label="Имя пользователя" Variant="Variant.Outlined" FullWidth="true" Class="mb-3" />
|
<MudTextField @bind-Value="_model.Username" Label="Имя пользователя" Variant="Variant.Outlined" FullWidth="true" Class="mb-3" />
|
||||||
<MudTextField @bind-Value="_model.Email" Label="Email" Variant="Variant.Outlined" FullWidth="true" Class="mb-3" />
|
<MudTextField @bind-Value="_model.Email" Label="Email" Variant="Variant.Outlined" FullWidth="true" Class="mb-3" />
|
||||||
<MudTextField @bind-Value="_model.Password" Label="Пароль" Variant="Variant.Outlined" FullWidth="true" Type="InputType.Password" />
|
<MudTextField @bind-Value="_model.Password" Label="Пароль" Variant="Variant.Outlined" FullWidth="true" InputType="InputType.Password" />
|
||||||
<MudTextField @bind-Value="_model.ConfirmPassword" Label="Подтверждение пароля" Variant="Variant.Outlined" FullWidth="true" Type="InputType.Password" />
|
<MudTextField @bind-Value="_model.ConfirmPassword" Label="Подтверждение пароля" Variant="Variant.Outlined" FullWidth="true" InputType="InputType.Password" />
|
||||||
|
|
||||||
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="OnRegister" FullWidth="true" Class="mt-4">
|
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="OnRegister" FullWidth="true" Class="mt-4">
|
||||||
Зарегистрироваться
|
Зарегистрироваться
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
@page "/shared/{token}"
|
@page "/shared/{token}"
|
||||||
@attribute [Authorize]
|
|
||||||
@using PlaylistShared.Shared.DTO
|
@using PlaylistShared.Shared.DTO
|
||||||
@using PlaylistShared.Shared.Enums
|
@using PlaylistShared.Shared.Enums
|
||||||
@using PlaylistShared.Pwa.Services
|
@using PlaylistShared.Pwa.Services
|
||||||
@@ -8,6 +7,7 @@
|
|||||||
@inject ISnackbar Snackbar
|
@inject ISnackbar Snackbar
|
||||||
@inject NavigationManager Navigation
|
@inject NavigationManager Navigation
|
||||||
@inject AuthenticationStateProvider AuthProvider
|
@inject AuthenticationStateProvider AuthProvider
|
||||||
|
@inject IDialogService DialogService
|
||||||
|
|
||||||
<MudContainer MaxWidth="MaxWidth.Large" Class="mt-8">
|
<MudContainer MaxWidth="MaxWidth.Large" Class="mt-8">
|
||||||
@if (_loading)
|
@if (_loading)
|
||||||
@@ -21,14 +21,25 @@
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
<MudCard>
|
<MudCard>
|
||||||
|
<!-- Заголовок с обложкой -->
|
||||||
<MudCardHeader>
|
<MudCardHeader>
|
||||||
<CardHeaderContent>
|
<CardHeaderContent>
|
||||||
|
<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" />
|
||||||
|
}
|
||||||
|
<div>
|
||||||
<MudText Typo="Typo.h5">@_playlist.Title</MudText>
|
<MudText Typo="Typo.h5">@_playlist.Title</MudText>
|
||||||
<MudText Typo="Typo.body2" Color="Color.Secondary">Владелец: @_playlist.Creator?.UserName</MudText>
|
<MudText Typo="Typo.body2" Color="Color.Secondary">Владелец: @_playlist.Creator?.UserName</MudText>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</CardHeaderContent>
|
</CardHeaderContent>
|
||||||
</MudCardHeader>
|
</MudCardHeader>
|
||||||
|
|
||||||
<MudCardContent>
|
<MudCardContent>
|
||||||
@if (_isCreator)
|
<!-- Настройки доступа (только для создателя, который авторизован) -->
|
||||||
|
@if (_isCreator && _isAuthenticated)
|
||||||
{
|
{
|
||||||
<MudPaper Class="pa-4 mb-4" Elevation="0" Style="background-color: rgba(0,0,0,0.05); border-radius: 8px;">
|
<MudPaper Class="pa-4 mb-4" Elevation="0" Style="background-color: rgba(0,0,0,0.05); border-radius: 8px;">
|
||||||
<MudText Typo="Typo.h6" GutterBottom>Настройки доступа</MudText>
|
<MudText Typo="Typo.h6" GutterBottom>Настройки доступа</MudText>
|
||||||
@@ -68,8 +79,85 @@
|
|||||||
</MudPaper>
|
</MudPaper>
|
||||||
}
|
}
|
||||||
|
|
||||||
<!-- Здесь будет отображение треков и управление -->
|
<!-- Блок добавления трека (только для авторизованных с правом добавления) -->
|
||||||
<MudText>Функционал управления треками в разработке</MudText>
|
@if (_canAdd)
|
||||||
|
{
|
||||||
|
<MudPaper Class="pa-4 mb-4" Elevation="0" Style="background-color: rgba(0,0,0,0.05); border-radius: 8px;">
|
||||||
|
<MudText Typo="Typo.h6" GutterBottom>Добавить трек</MudText>
|
||||||
|
<MudGrid>
|
||||||
|
<MudItem xs="12" sm="10">
|
||||||
|
<MudTextField @bind-Value="_trackLink" Label="Ссылка на трек Яндекс.Музыки"
|
||||||
|
Variant="Variant.Outlined" FullWidth="true"
|
||||||
|
Placeholder="https://music.yandex.ru/album/2488464/track/21696942" />
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="12" sm="2">
|
||||||
|
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="AddTrack"
|
||||||
|
Disabled="_addingTrack" FullWidth="true" Style="height: 100%;">
|
||||||
|
@if (_addingTrack)
|
||||||
|
{
|
||||||
|
<MudProgressCircular Size="Size.Small" Indeterminate />
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
|
||||||
|
<span>Добавить</span>
|
||||||
|
}
|
||||||
|
</MudButton>
|
||||||
|
</MudItem>
|
||||||
|
</MudGrid>
|
||||||
|
<MudText Typo="Typo.body2" Class="mt-2" Color="Color.Secondary">
|
||||||
|
Поддерживаются ссылки вида: https://music.yandex.ru/album/12345/track/67890
|
||||||
|
</MudText>
|
||||||
|
</MudPaper>
|
||||||
|
}
|
||||||
|
|
||||||
|
<!-- Список треков -->
|
||||||
|
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||||
|
<MudText Typo="Typo.h6" GutterBottom>Треки</MudText>
|
||||||
|
<MudIconButton Icon="@Icons.Material.Filled.Refresh" OnClick="LoadTracks" Disabled="_tracksLoading" Size="Size.Small" />
|
||||||
|
</div>
|
||||||
|
@if (_tracksLoading)
|
||||||
|
{
|
||||||
|
<MudProgressCircular Indeterminate />
|
||||||
|
}
|
||||||
|
else if (_tracks == null || !_tracks.Any())
|
||||||
|
{
|
||||||
|
<MudAlert Severity="Severity.Info">В плейлисте пока нет треков</MudAlert>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudTable Items="@_tracks" Hover="true" Breakpoint="Breakpoint.Sm">
|
||||||
|
<HeaderContent>
|
||||||
|
<MudTh>#</MudTh>
|
||||||
|
<MudTh>Обложка</MudTh>
|
||||||
|
<MudTh>Название</MudTh>
|
||||||
|
<MudTh>Исполнитель</MudTh>
|
||||||
|
<MudTh>Длительность</MudTh>
|
||||||
|
@if (_canRemove)
|
||||||
|
{
|
||||||
|
<MudTh></MudTh>
|
||||||
|
}
|
||||||
|
</HeaderContent>
|
||||||
|
<RowTemplate>
|
||||||
|
<MudTd>@context.Index</MudTd>
|
||||||
|
<MudTd>
|
||||||
|
@if (!string.IsNullOrEmpty(context.CoverUri))
|
||||||
|
{
|
||||||
|
<MudImage Src="@FormatCoverUrl(context.CoverUri, "50x50")" Height="50" Width="50" Class="rounded" />
|
||||||
|
}
|
||||||
|
</MudTd>
|
||||||
|
<MudTd>@context.Title</MudTd>
|
||||||
|
<MudTd>@string.Join(", ", context.Artists)</MudTd>
|
||||||
|
<MudTd>@FormatDuration(context.DurationMs)</MudTd>
|
||||||
|
@if (_isAuthenticated && _canRemove)
|
||||||
|
{
|
||||||
|
<MudTd>
|
||||||
|
<MudIconButton Icon="@Icons.Material.Filled.Delete" Color="Color.Error" OnClick="() => RemoveTrack(context)" />
|
||||||
|
</MudTd>
|
||||||
|
}
|
||||||
|
</RowTemplate>
|
||||||
|
</MudTable>
|
||||||
|
}
|
||||||
</MudCardContent>
|
</MudCardContent>
|
||||||
</MudCard>
|
</MudCard>
|
||||||
}
|
}
|
||||||
@@ -80,14 +168,24 @@
|
|||||||
|
|
||||||
private SharedPlaylistDto? _playlist;
|
private SharedPlaylistDto? _playlist;
|
||||||
private bool _loading = true;
|
private bool _loading = true;
|
||||||
|
private bool _isAuthenticated;
|
||||||
private bool _isCreator;
|
private bool _isCreator;
|
||||||
|
private bool _canAdd;
|
||||||
|
private bool _canRemove;
|
||||||
private UpdatePermissionsDto _editPermissions = new();
|
private UpdatePermissionsDto _editPermissions = new();
|
||||||
private bool _savingPermissions;
|
private bool _savingPermissions;
|
||||||
private string? _currentUserId;
|
private string? _currentUserId;
|
||||||
|
|
||||||
|
private List<YandexTrackDisplay> _tracks = new();
|
||||||
|
private bool _tracksLoading;
|
||||||
|
|
||||||
|
private string _trackLink = "";
|
||||||
|
private bool _addingTrack;
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
var authState = await AuthProvider.GetAuthenticationStateAsync();
|
var authState = await AuthProvider.GetAuthenticationStateAsync();
|
||||||
|
_isAuthenticated = authState.User.Identity?.IsAuthenticated == true;
|
||||||
_currentUserId = authState.User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value;
|
_currentUserId = authState.User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value;
|
||||||
await LoadPlaylist();
|
await LoadPlaylist();
|
||||||
}
|
}
|
||||||
@@ -101,7 +199,16 @@
|
|||||||
{
|
{
|
||||||
_playlist = response.Data;
|
_playlist = response.Data;
|
||||||
_isCreator = _playlist.CreatorUserId.ToString() == _currentUserId;
|
_isCreator = _playlist.CreatorUserId.ToString() == _currentUserId;
|
||||||
if (_isCreator)
|
|
||||||
|
_canAdd = _isCreator
|
||||||
|
|| _playlist.AddPermission == EditPermission.Everyone
|
||||||
|
|| (_playlist.AddPermission == EditPermission.AuthorizedOnly && _isAuthenticated);
|
||||||
|
|
||||||
|
_canRemove = _isCreator
|
||||||
|
|| _playlist.RemovePermission == EditPermission.Everyone
|
||||||
|
|| (_playlist.RemovePermission == EditPermission.AuthorizedOnly && _isAuthenticated);
|
||||||
|
|
||||||
|
if (_isCreator && _isAuthenticated)
|
||||||
{
|
{
|
||||||
_editPermissions = new UpdatePermissionsDto
|
_editPermissions = new UpdatePermissionsDto
|
||||||
{
|
{
|
||||||
@@ -110,6 +217,8 @@
|
|||||||
RemovePermission = _playlist.RemovePermission
|
RemovePermission = _playlist.RemovePermission
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await LoadTracks();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -127,8 +236,121 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task LoadTracks()
|
||||||
|
{
|
||||||
|
if (_playlist == null) return;
|
||||||
|
_tracksLoading = true;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var url = $"/api/sharedplaylist/{Token}/tracks";
|
||||||
|
var response = await Http.GetFromJsonAsync<ApiResponse<YandexPlaylistData>>(url);
|
||||||
|
if (response?.Success == true && response.Data != null)
|
||||||
|
{
|
||||||
|
_tracks = response.Data.Tracks.Select((t, idx) => new YandexTrackDisplay
|
||||||
|
{
|
||||||
|
Id = t.Id,
|
||||||
|
Title = t.Title,
|
||||||
|
Artists = t.Artists,
|
||||||
|
DurationMs = t.DurationMs,
|
||||||
|
CoverUri = t.CoverUri,
|
||||||
|
Index = idx + 1
|
||||||
|
}).ToList();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Snackbar.Add(response?.Error?.Message ?? "Не удалось загрузить треки", Severity.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Snackbar.Add($"Ошибка загрузки треков: {ex.Message}", Severity.Error);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_tracksLoading = false;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task AddTrack()
|
||||||
|
{
|
||||||
|
if (!_isAuthenticated)
|
||||||
|
{
|
||||||
|
Snackbar.Add("Для добавления треков необходимо войти", Severity.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (string.IsNullOrWhiteSpace(_trackLink))
|
||||||
|
{
|
||||||
|
Snackbar.Add("Введите ссылку на трек", Severity.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_addingTrack = true;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var request = new AddTrackByLinkRequest { Link = _trackLink };
|
||||||
|
var response = await Http.PostAsJsonAsync($"/api/sharedplaylist/{Token}/add-track-by-link", request);
|
||||||
|
if (response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
Snackbar.Add("Трек успешно добавлен", Severity.Success);
|
||||||
|
_trackLink = "";
|
||||||
|
await LoadTracks();
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_addingTrack = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task RemoveTrack(YandexTrackDisplay track)
|
||||||
|
{
|
||||||
|
if (!_isAuthenticated)
|
||||||
|
{
|
||||||
|
Snackbar.Add("Для удаления треков необходимо войти", Severity.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var confirmed = await DialogService.ShowMessageBoxAsync(
|
||||||
|
"Подтверждение удаления",
|
||||||
|
$"Вы уверены, что хотите удалить трек \"{track.Title}\"?",
|
||||||
|
yesText: "Удалить", cancelText: "Отмена");
|
||||||
|
|
||||||
|
if (confirmed != true) return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var request = new RemoveTracksRequest { TrackIds = new List<string> { track.Id } };
|
||||||
|
var response = await Http.PostAsJsonAsync($"/api/sharedplaylist/{Token}/remove-tracks", request);
|
||||||
|
if (response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
Snackbar.Add("Трек удалён", Severity.Success);
|
||||||
|
await LoadTracks();
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task SavePermissions()
|
private async Task SavePermissions()
|
||||||
{
|
{
|
||||||
|
if (!_isAuthenticated) return;
|
||||||
_savingPermissions = true;
|
_savingPermissions = true;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -140,6 +362,10 @@
|
|||||||
{
|
{
|
||||||
_playlist = result.Data;
|
_playlist = result.Data;
|
||||||
Snackbar.Add("Права доступа обновлены", Severity.Success);
|
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);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -160,4 +386,23 @@
|
|||||||
_savingPermissions = false;
|
_savingPermissions = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string FormatDuration(int ms)
|
||||||
|
{
|
||||||
|
var seconds = ms / 1000;
|
||||||
|
var mins = seconds / 60;
|
||||||
|
var secs = seconds % 60;
|
||||||
|
return $"{mins}:{secs:D2}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private string FormatCoverUrl(string? url, string size = "200x200")
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(url)) return "";
|
||||||
|
return "https://" + url.Replace("%%", size);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class YandexTrackDisplay : YandexTrack
|
||||||
|
{
|
||||||
|
public int Index { get; set; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"ApiBaseUrl": ""
|
"ApiBaseUrl": "https://api.playlistshare.frigat.duckdns.org"
|
||||||
}
|
}
|
||||||
6
PlaylistShared.Shared/DTO/AddTrackByLinkRequest.cs
Normal file
6
PlaylistShared.Shared/DTO/AddTrackByLinkRequest.cs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
namespace PlaylistShared.Shared.DTO;
|
||||||
|
|
||||||
|
public class AddTrackByLinkRequest
|
||||||
|
{
|
||||||
|
public string Link { get; set; }
|
||||||
|
}
|
||||||
@@ -2,11 +2,8 @@
|
|||||||
|
|
||||||
namespace PlaylistShared.Shared.DTO;
|
namespace PlaylistShared.Shared.DTO;
|
||||||
|
|
||||||
public class AddTrackRequest
|
public class AddTracksRequest
|
||||||
{
|
{
|
||||||
[JsonPropertyName("sharedPlaylistToken")]
|
|
||||||
public string SharedPlaylistToken { get; set; } = null!;
|
|
||||||
|
|
||||||
[JsonPropertyName("trackIds")]
|
[JsonPropertyName("trackIds")]
|
||||||
public List<string> TrackIds { get; set; } = new();
|
public List<string> TrackIds { get; set; } = new();
|
||||||
}
|
}
|
||||||
9
PlaylistShared.Shared/DTO/RemoveTracksRequest.cs
Normal file
9
PlaylistShared.Shared/DTO/RemoveTracksRequest.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace PlaylistShared.Shared.DTO;
|
||||||
|
|
||||||
|
public class RemoveTracksRequest
|
||||||
|
{
|
||||||
|
[JsonPropertyName("trackIds")]
|
||||||
|
public List<string> TrackIds { get; set; } = new();
|
||||||
|
}
|
||||||
8
PlaylistShared.Shared/DTO/YandexPlaylistData.cs
Normal file
8
PlaylistShared.Shared/DTO/YandexPlaylistData.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace PlaylistShared.Shared.DTO;
|
||||||
|
|
||||||
|
public class YandexPlaylistData
|
||||||
|
{
|
||||||
|
public string Title { get; set; } = "";
|
||||||
|
public string Description { get; set; } = "";
|
||||||
|
public List<YandexTrack> Tracks { get; set; } = new();
|
||||||
|
}
|
||||||
10
PlaylistShared.Shared/DTO/YandexTrack.cs
Normal file
10
PlaylistShared.Shared/DTO/YandexTrack.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
namespace PlaylistShared.Shared.DTO;
|
||||||
|
|
||||||
|
public class YandexTrack
|
||||||
|
{
|
||||||
|
public string Id { get; set; } = "";
|
||||||
|
public string Title { get; set; } = "";
|
||||||
|
public List<string> Artists { get; set; } = new();
|
||||||
|
public int DurationMs { get; set; }
|
||||||
|
public string CoverUri { get; set; } = "";
|
||||||
|
}
|
||||||
@@ -1,2 +1,2 @@
|
|||||||
docker-compose up -d --force-recreate
|
docker-compose up -d --build --force-recreate
|
||||||
pause
|
pause
|
||||||
Reference in New Issue
Block a user