From 6ae49faf155bde38d83967f6a8016b91c2f44fae Mon Sep 17 00:00:00 2001 From: FrigaT Date: Tue, 14 Apr 2026 22:26:58 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D1=80=D0=B0=D0=B1=D0=BE=D1=82?= =?UTF-8?q?=D0=B0=D0=BD=20=D0=BF=D0=BE=D0=B8=D1=81=D0=BA=20=D0=B8=20=D0=B4?= =?UTF-8?q?=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D1=82?= =?UTF-8?q?=D1=80=D0=B5=D0=BA=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/AudioController.cs | 6 ++-- .../Controllers/SharedPlaylistController.cs | 2 +- .../Controllers/YandexSearchController.cs | 8 ++--- PlaylistShared.Api/PlaylistShared.Api.csproj | 20 +++++------ .../Services/YandexMusicService.cs | 20 ++++++----- .../SharedPlaylist/AddTrackBySearch.razor | 34 ++++++++----------- .../SharedPlaylist/AddTrackSection.razor | 6 ++-- .../SharedPlaylist/TracksTable.razor | 18 +++------- .../Extensions/LongExtensions.cs | 15 ++++++++ .../Extensions/StringExtensions.cs | 2 +- PlaylistShared.Pwa/Layout/MainLayout.razor | 1 - PlaylistShared.Pwa/Pages/Login.razor | 8 ++++- .../Services/AudioPlayerService.cs | 2 +- PlaylistShared.Shared/DTO/TrackInfoDto.cs | 7 ---- PlaylistShared.Shared/DTO/YandexTrack.cs | 20 ++++++++--- .../DTO/YandexTrackSearchResult.cs | 22 ------------ 16 files changed, 92 insertions(+), 99 deletions(-) create mode 100644 PlaylistShared.Pwa/Extensions/LongExtensions.cs delete mode 100644 PlaylistShared.Shared/DTO/TrackInfoDto.cs delete mode 100644 PlaylistShared.Shared/DTO/YandexTrackSearchResult.cs diff --git a/PlaylistShared.Api/Controllers/AudioController.cs b/PlaylistShared.Api/Controllers/AudioController.cs index 0491a80..2530bdf 100644 --- a/PlaylistShared.Api/Controllers/AudioController.cs +++ b/PlaylistShared.Api/Controllers/AudioController.cs @@ -74,7 +74,7 @@ public class AudioController : ControllerBase [HttpGet("track-info/{trackId}")] [AllowAnonymous] - public async Task>> GetTrackInfo(string trackId, [FromQuery] string? access_token = null, [FromQuery] string? shared_id = null) + public async Task>> GetTrackInfo(string trackId, [FromQuery] string? access_token = null, [FromQuery] string? shared_id = null) { var user = await GetUserFromToken(access_token); if (user == null) user = await GetUserFromSharedPlaylistId(shared_id); @@ -83,10 +83,12 @@ public class AudioController : ControllerBase var track = await _yandexService.GetYTrackAsync(user, trackId); if (track == null) return NotFound(); - return Ok(ApiResponse.Ok(new TrackInfoDto + return Ok(ApiResponse.Ok(new YandexTrack { Title = track.Title, CoverUri = track.CoverUri, + Artists = track.Artists.Select(t => t.Name).ToList(), + DurationMs = track.DurationMs, })); } diff --git a/PlaylistShared.Api/Controllers/SharedPlaylistController.cs b/PlaylistShared.Api/Controllers/SharedPlaylistController.cs index ba86fea..c37f758 100644 --- a/PlaylistShared.Api/Controllers/SharedPlaylistController.cs +++ b/PlaylistShared.Api/Controllers/SharedPlaylistController.cs @@ -189,7 +189,7 @@ public class SharedPlaylistController : ControllerBase Description = playlist.Description ?? "", Tracks = playlist.Tracks?.Select(t => new YandexTrack { - Id = t.Track?.Id ?? "", + TrackId = t.Track?.Id ?? "", Title = t.Track?.Title ?? "", Artists = t.Track?.Artists?.Select(a => a.Name).ToList() ?? new List(), DurationMs = (int)(t.Track?.DurationMs ?? 0), diff --git a/PlaylistShared.Api/Controllers/YandexSearchController.cs b/PlaylistShared.Api/Controllers/YandexSearchController.cs index 54d0cce..94922ac 100644 --- a/PlaylistShared.Api/Controllers/YandexSearchController.cs +++ b/PlaylistShared.Api/Controllers/YandexSearchController.cs @@ -26,13 +26,13 @@ public class YandexSearchController : ControllerBase } [HttpGet("tracks")] - public async Task>>> SearchTracks( + public async Task>>> SearchTracks( [FromQuery] string query, [FromQuery] int limit = 20, [FromQuery] string? shared_id = null) { if (string.IsNullOrWhiteSpace(query)) - return BadRequest(ApiResponse>.Fail(new ErrorResponse + return BadRequest(ApiResponse>.Fail(new ErrorResponse { StatusCode = 400, Message = "Поисковый запрос не может быть пустым." @@ -62,13 +62,13 @@ public class YandexSearchController : ControllerBase var decryptedToken = _yandexService.DecryptToken(user.YandexAccessToken); if (string.IsNullOrEmpty(decryptedToken)) - return BadRequest(ApiResponse>.Fail(new ErrorResponse + return BadRequest(ApiResponse>.Fail(new ErrorResponse { StatusCode = 400, Message = "Токен Яндекс.Музыки не установлен или недействителен." })); var results = await _yandexService.SearchTracksAsync(user, query, limit); - return Ok(ApiResponse>.Ok(results)); + return Ok(ApiResponse>.Ok(results)); } } \ No newline at end of file diff --git a/PlaylistShared.Api/PlaylistShared.Api.csproj b/PlaylistShared.Api/PlaylistShared.Api.csproj index 049e6ab..f6c7255 100644 --- a/PlaylistShared.Api/PlaylistShared.Api.csproj +++ b/PlaylistShared.Api/PlaylistShared.Api.csproj @@ -10,24 +10,24 @@ - - - - - - + + + + + + - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + - + diff --git a/PlaylistShared.Api/Services/YandexMusicService.cs b/PlaylistShared.Api/Services/YandexMusicService.cs index 63c2ea6..cb1f783 100644 --- a/PlaylistShared.Api/Services/YandexMusicService.cs +++ b/PlaylistShared.Api/Services/YandexMusicService.cs @@ -55,12 +55,16 @@ public class YandexMusicService { var client = await CreateClientAsync(user); if (client == null) return null; - // Получаем треки по ID - var tracks = await client.GetTracksAsync(trackIds); - if (tracks == null || !tracks.Any()) return null; + var playlist = await client.GetPlaylistAsync(ownerUid, kind); if (playlist == null) return null; - return await playlist.InsertTracksAsync(tracks.ToArray()); + + var tracks = await client.GetTracksAsync(trackIds); + if (tracks == null || !tracks.Any()) return null; + + var insertedTracks = tracks.Where(t => !playlist.Tracks.Any(p => p.Track.Id == t.Id)).ToArray(); + + return await playlist.InsertTracksAsync(insertedTracks); } public async Task RemoveTracksAsync(ApplicationUser user, string ownerUid, string kind, IEnumerable trackIds) @@ -103,15 +107,15 @@ public class YandexMusicService } } - public async Task> SearchTracksAsync(ApplicationUser user, string query, int limit = 20) + public async Task> SearchTracksAsync(ApplicationUser user, string query, int limit = 20) { var client = await CreateClientAsync(user); - if (client == null) return new List(); + if (client == null) return new List(); var searchResult = await client.SearchAsync(query, YandexMusic.API.Models.Common.YSearchType.Track, page: 0, pageSize: limit); - if (searchResult?.Tracks?.Results == null) return new List(); + if (searchResult?.Tracks?.Results == null) return new List(); - return searchResult.Tracks.Results.Select(t => new YandexTrackSearchResult + return searchResult.Tracks.Results.Select(t => new YandexTrack { TrackId = t.Id, Title = t.Title, diff --git a/PlaylistShared.Pwa/Components/SharedPlaylist/AddTrackBySearch.razor b/PlaylistShared.Pwa/Components/SharedPlaylist/AddTrackBySearch.razor index 4632e25..b758d3f 100644 --- a/PlaylistShared.Pwa/Components/SharedPlaylist/AddTrackBySearch.razor +++ b/PlaylistShared.Pwa/Components/SharedPlaylist/AddTrackBySearch.razor @@ -4,14 +4,12 @@ @inject ISnackbar Snackbar - Поиск трека -
@string.Join(", ", track.Artists)
- @FormatDuration(track.DurationMs) + @track.DurationMs.FormatDuration()
} - else if (!string.IsNullOrEmpty(_searchQuery) && !_isSearching) + else if (!_isFirstSearch) { Ничего не найдено. Попробуйте изменить запрос. } -@code { + @code { [Parameter] public EventCallback OnAddTrack { get; set; } [Parameter] public string ShareToken { get; set; } = string.Empty; - private string _searchQuery = ""; - private List _searchResults = new(); + private List _searchResults = new(); private bool _isSearching; + private bool _isFirstSearch = true; private HashSet _addingTrackIds = new(); + private string _searchQuery = string.Empty; private async Task SearchTracks() { - if (string.IsNullOrWhiteSpace(_searchQuery)) return; - + + if (string.IsNullOrWhiteSpace(_searchQuery)) + return; + + _isFirstSearch = false; _isSearching = true; try { @@ -83,7 +85,7 @@ if (!string.IsNullOrEmpty(ShareToken)) url += $"&shared_id={Uri.EscapeDataString(ShareToken)}"; - var response = await Http.GetFromJsonAsync>>(url); + var response = await Http.GetFromJsonAsync>>(url); if (response?.Success == true) _searchResults = response.Data ?? new(); else @@ -100,7 +102,7 @@ } } - private async Task AddTrack(YandexTrackSearchResult track) + private async Task AddTrack(YandexTrack track) { if (_addingTrackIds.Contains(track.TrackId)) return; _addingTrackIds.Add(track.TrackId); @@ -119,12 +121,4 @@ StateHasChanged(); } } - - private string FormatDuration(long ms) - { - var seconds = ms / 1000; - var mins = seconds / 60; - var secs = seconds % 60; - return $"{mins}:{secs:D2}"; - } } \ No newline at end of file diff --git a/PlaylistShared.Pwa/Components/SharedPlaylist/AddTrackSection.razor b/PlaylistShared.Pwa/Components/SharedPlaylist/AddTrackSection.razor index fcdca5c..88d99b7 100644 --- a/PlaylistShared.Pwa/Components/SharedPlaylist/AddTrackSection.razor +++ b/PlaylistShared.Pwa/Components/SharedPlaylist/AddTrackSection.razor @@ -9,7 +9,8 @@ + Placeholder="https://music.yandex.ru/album/2488464/track/21696942" + OnKeyUp="@(async (e) => { if (e.Key == "Enter") await AddTrackByLink(); })" /> - - Поддерживаются ссылки вида: https://music.yandex.ru/album/12345/track/67890 - } else @@ -35,13 +35,13 @@ } - + @context.Title @string.Join(", ", context.Artists) - @FormatDuration(context.DurationMs) + @context.DurationMs.FormatDuration() @if (CanRemove) { @@ -82,7 +82,7 @@ { _tracks = response.Data.Tracks.Select((t, idx) => new TrackDisplay { - Id = t.Id, + TrackId = t.TrackId, Title = t.Title, Artists = t.Artists, DurationMs = t.DurationMs, @@ -117,7 +117,7 @@ try { - var request = new RemoveTracksRequest { TrackIds = new List { track.Id } }; + var request = new RemoveTracksRequest { TrackIds = new List { track.TrackId } }; var response = await Http.PostAsJsonAsync($"/api/sharedplaylist/{ShareToken}/remove-tracks", request); if (response.IsSuccessStatusCode) { @@ -141,14 +141,6 @@ await OnPlayTrack.InvokeAsync(trackId); } - private string FormatDuration(long ms) - { - var seconds = ms / 1000; - var mins = seconds / 60; - var secs = seconds % 60; - return $"{mins}:{secs:D2}"; - } - private class TrackDisplay : YandexTrack { public int Index { get; set; } diff --git a/PlaylistShared.Pwa/Extensions/LongExtensions.cs b/PlaylistShared.Pwa/Extensions/LongExtensions.cs new file mode 100644 index 0000000..acd101f --- /dev/null +++ b/PlaylistShared.Pwa/Extensions/LongExtensions.cs @@ -0,0 +1,15 @@ +namespace PlaylistShared.Pwa.Extensions; + +public static class LongExtensions +{ + /// + /// Преобразует миллисекунды в формат Минуты:Секунды + /// + public static string FormatDuration(this long ms) + { + var seconds = ms / 1000; + var mins = seconds / 60; + var secs = seconds % 60; + return $"{mins}:{secs:D2}"; + } +} \ No newline at end of file diff --git a/PlaylistShared.Pwa/Extensions/StringExtensions.cs b/PlaylistShared.Pwa/Extensions/StringExtensions.cs index 42f0897..c222eb4 100644 --- a/PlaylistShared.Pwa/Extensions/StringExtensions.cs +++ b/PlaylistShared.Pwa/Extensions/StringExtensions.cs @@ -16,4 +16,4 @@ public static class StringExtensions return "https://" + coverUri.Replace("%%", $"{width}x{height}"); } -} \ No newline at end of file +} diff --git a/PlaylistShared.Pwa/Layout/MainLayout.razor b/PlaylistShared.Pwa/Layout/MainLayout.razor index 0cdbcbf..346cd8a 100644 --- a/PlaylistShared.Pwa/Layout/MainLayout.razor +++ b/PlaylistShared.Pwa/Layout/MainLayout.razor @@ -9,7 +9,6 @@ - Playlist share diff --git a/PlaylistShared.Pwa/Pages/Login.razor b/PlaylistShared.Pwa/Pages/Login.razor index 7712a1e..0f6aa52 100644 --- a/PlaylistShared.Pwa/Pages/Login.razor +++ b/PlaylistShared.Pwa/Pages/Login.razor @@ -25,7 +25,7 @@ - + Войти (локально) @@ -48,6 +48,12 @@ private async Task LocalLogin() { + if (string.IsNullOrWhiteSpace(_loginModel.Username) || string.IsNullOrWhiteSpace(_loginModel.Password)) + { + Snackbar.Add("Пожалуйста, заполните все поля", Severity.Warning); + return; + } + var response = await Http.PostAsJsonAsync("/api/account/login", _loginModel); if (response.IsSuccessStatusCode) { diff --git a/PlaylistShared.Pwa/Services/AudioPlayerService.cs b/PlaylistShared.Pwa/Services/AudioPlayerService.cs index 7c669cc..dc56560 100644 --- a/PlaylistShared.Pwa/Services/AudioPlayerService.cs +++ b/PlaylistShared.Pwa/Services/AudioPlayerService.cs @@ -176,7 +176,7 @@ public class AudioPlayerService : IAudioPlayerService else if (!string.IsNullOrEmpty(sharedPlaylistId)) url += $"?shared_id={sharedPlaylistId}"; - var response = await _http.GetFromJsonAsync>(url); + var response = await _http.GetFromJsonAsync>(url); if (response?.Success == true) { return (response.Data.Title, response.Data.CoverUri); diff --git a/PlaylistShared.Shared/DTO/TrackInfoDto.cs b/PlaylistShared.Shared/DTO/TrackInfoDto.cs deleted file mode 100644 index e073bea..0000000 --- a/PlaylistShared.Shared/DTO/TrackInfoDto.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace PlaylistShared.Shared.DTO; - -public class TrackInfoDto -{ - public string Title { get; set; } - public string CoverUri { get; set; } -} diff --git a/PlaylistShared.Shared/DTO/YandexTrack.cs b/PlaylistShared.Shared/DTO/YandexTrack.cs index b65a792..692c628 100644 --- a/PlaylistShared.Shared/DTO/YandexTrack.cs +++ b/PlaylistShared.Shared/DTO/YandexTrack.cs @@ -1,10 +1,22 @@ -namespace PlaylistShared.Shared.DTO; +using System.Text.Json.Serialization; +namespace PlaylistShared.Shared.DTO; + +/// Результат поиска трека в Яндекс.Музыке. public class YandexTrack { - public string Id { get; set; } = ""; - public string Title { get; set; } = ""; + [JsonPropertyName("trackId")] + public string TrackId { get; set; } = string.Empty; + + [JsonPropertyName("title")] + public string Title { get; set; } = string.Empty; + + [JsonPropertyName("artists")] public List Artists { get; set; } = new(); + + [JsonPropertyName("coverUri")] + public string CoverUri { get; set; } = string.Empty; + + [JsonPropertyName("durationMs")] public long DurationMs { get; set; } - public string CoverUri { get; set; } = ""; } \ No newline at end of file diff --git a/PlaylistShared.Shared/DTO/YandexTrackSearchResult.cs b/PlaylistShared.Shared/DTO/YandexTrackSearchResult.cs deleted file mode 100644 index 34f32cc..0000000 --- a/PlaylistShared.Shared/DTO/YandexTrackSearchResult.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Text.Json.Serialization; - -namespace PlaylistShared.Shared.DTO; - -/// Результат поиска трека в Яндекс.Музыке. -public class YandexTrackSearchResult -{ - [JsonPropertyName("trackId")] - public string TrackId { get; set; } = string.Empty; - - [JsonPropertyName("title")] - public string Title { get; set; } = string.Empty; - - [JsonPropertyName("artists")] - public List Artists { get; set; } = new(); - - [JsonPropertyName("coverUri")] - public string CoverUri { get; set; } = string.Empty; - - [JsonPropertyName("durationMs")] - public long DurationMs { get; set; } -} \ No newline at end of file