| @@ -0,0 +1,16 @@ | |||
| using ProtoBuf; | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace GrpcShared.DTO | |||
| { | |||
| [ProtoContract] | |||
| public class TokenMessage | |||
| { | |||
| [ProtoMember(1)] | |||
| public string? Token { get; set; } | |||
| } | |||
| } | |||
| @@ -0,0 +1,18 @@ | |||
| using ProtoBuf; | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace GrpcShared.DTO.Track.MultipleTrack | |||
| { | |||
| [ProtoContract] | |||
| public class MultipleTrackRequest | |||
| { | |||
| [ProtoMember(1)] | |||
| public List<string> Ids { get; set; } | |||
| [ProtoMember(2)] | |||
| public string Token { get; set; } | |||
| } | |||
| } | |||
| @@ -0,0 +1,74 @@ | |||
| using ProtoBuf; | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace GrpcShared.DTO.Track.MultipleTrack | |||
| { | |||
| [ProtoContract] | |||
| public class MultipleTrackResponse | |||
| { | |||
| [ProtoMember(1)] | |||
| public List<AudioFeature>? Audio_Features { get; set; } | |||
| } | |||
| [ProtoContract] | |||
| public class AudioFeature | |||
| { | |||
| [ProtoMember(1)] | |||
| public double Danceability { get; set; } | |||
| [ProtoMember(2)] | |||
| public double Energy { get; set; } | |||
| [ProtoMember(3)] | |||
| public long Key { get; set; } | |||
| [ProtoMember(4)] | |||
| public double Loudness { get; set; } | |||
| [ProtoMember(5)] | |||
| public long Mode { get; set; } | |||
| [ProtoMember(6)] | |||
| public double Speechiness { get; set; } | |||
| [ProtoMember(7)] | |||
| public double Acousticness { get; set; } | |||
| [ProtoMember(8)] | |||
| public long Instrumentalness { get; set; } | |||
| [ProtoMember(9)] | |||
| public double Liveness { get; set; } | |||
| [ProtoMember(10)] | |||
| public double Valence { get; set; } | |||
| [ProtoMember(11)] | |||
| public double Tempo { get; set; } | |||
| [ProtoMember(12)] | |||
| public string Type { get; set; } | |||
| [ProtoMember(13)] | |||
| public string Id { get; set; } | |||
| [ProtoMember(14)] | |||
| public string Uri { get; set; } | |||
| [ProtoMember(15)] | |||
| public Uri TrackHref { get; set; } | |||
| [ProtoMember(16)] | |||
| public Uri AnalysisUrl { get; set; } | |||
| [ProtoMember(17)] | |||
| public long DurationMs { get; set; } | |||
| [ProtoMember(18)] | |||
| public long TimeSignature { get; set; } | |||
| } | |||
| } | |||
| @@ -0,0 +1,18 @@ | |||
| using ProtoBuf; | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace GrpcShared.DTO.Track.SaveTracks | |||
| { | |||
| [ProtoContract] | |||
| public class SaveTracksRequest | |||
| { | |||
| [ProtoMember(1)] | |||
| public List<string> Ids { get; set; } | |||
| [ProtoMember(2)] | |||
| public string Token { get; set; } | |||
| } | |||
| } | |||
| @@ -0,0 +1,18 @@ | |||
| using ProtoBuf; | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace GrpcShared.DTO.Track.SingleTrack | |||
| { | |||
| [ProtoContract] | |||
| public class SingleTrackRequest | |||
| { | |||
| [ProtoMember(1)] | |||
| public string Id { get; set; } | |||
| [ProtoMember(2)] | |||
| public string Token { get; set; } | |||
| } | |||
| } | |||
| @@ -0,0 +1,67 @@ | |||
| using ProtoBuf; | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace GrpcShared.DTO.Track.SingleTrack | |||
| { | |||
| [ProtoContract] | |||
| public class SingleTrackResponse | |||
| { | |||
| [ProtoMember(1)] | |||
| public double Danceability { get; set; } | |||
| [ProtoMember(2)] | |||
| public double Energy { get; set; } | |||
| [ProtoMember(3)] | |||
| public long Key { get; set; } | |||
| [ProtoMember(4)] | |||
| public double Loudness { get; set; } | |||
| [ProtoMember(5)] | |||
| public long Mode { get; set; } | |||
| [ProtoMember(6)] | |||
| public double Speechiness { get; set; } | |||
| [ProtoMember(7)] | |||
| public double Acousticness { get; set; } | |||
| [ProtoMember(8)] | |||
| public long Instrumentalness { get; set; } | |||
| [ProtoMember(9)] | |||
| public double Liveness { get; set; } | |||
| [ProtoMember(10)] | |||
| public double Valence { get; set; } | |||
| [ProtoMember(11)] | |||
| public double Tempo { get; set; } | |||
| [ProtoMember(12)] | |||
| public string Type { get; set; } | |||
| [ProtoMember(13)] | |||
| public string Id { get; set; } | |||
| [ProtoMember(14)] | |||
| public string Uri { get; set; } | |||
| [ProtoMember(15)] | |||
| public Uri TrackHref { get; set; } | |||
| [ProtoMember(16)] | |||
| public Uri AnalysisUrl { get; set; } | |||
| [ProtoMember(17)] | |||
| public long DurationMs { get; set; } | |||
| [ProtoMember(18)] | |||
| public long TimeSignature { get; set; } | |||
| } | |||
| } | |||
| @@ -0,0 +1,20 @@ | |||
| using ProtoBuf; | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace GrpcShared.DTO.User | |||
| { | |||
| [ProtoContract] | |||
| public class UserInfoResponse | |||
| { | |||
| [ProtoMember(1)] | |||
| public string? email { get; set; } | |||
| [ProtoMember(2)] | |||
| public string? id { get; set; } | |||
| [ProtoMember(3)] | |||
| public string? display_name { get; set; } | |||
| } | |||
| } | |||
| @@ -0,0 +1,9 @@ | |||
| using System; | |||
| namespace Shared | |||
| { | |||
| public static class GLOBALS | |||
| { | |||
| public const String SPOTIFYURL = "https://api.spotify.com/v1/"; | |||
| public const String MEDIATYPE = "application/json"; | |||
| } | |||
| } | |||
| @@ -8,7 +8,9 @@ | |||
| </PropertyGroup> | |||
| <ItemGroup> | |||
| <PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" /> | |||
| <PackageReference Include="Microsoft.Extensions.Configuration" Version="6.0.0" /> | |||
| <PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> | |||
| <PackageReference Include="protobuf-net.BuildTools" Version="3.1.17"> | |||
| <PrivateAssets>all</PrivateAssets> | |||
| <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | |||
| @@ -1,4 +1,6 @@ | |||
| using GrpcShared.DTO.Auth; | |||
| using GrpcShared.DTO; | |||
| using GrpcShared.DTO.Auth; | |||
| using GrpcShared.DTO.User; | |||
| using ProtoBuf.Grpc.Configuration; | |||
| namespace GrpcShared.Interfaces | |||
| @@ -8,6 +10,7 @@ namespace GrpcShared.Interfaces | |||
| { | |||
| Task<TokenResponse> GetAccessToken(TokenRequest code); | |||
| Task<CodeRequest> GetAuthParams(); | |||
| Task<UserInfoResponse> GetUserInfo(TokenMessage token); | |||
| //Task<ClientSecrets> GetClientSecrets(); | |||
| } | |||
| } | |||
| @@ -1,4 +1,7 @@ | |||
| using GrpcShared.DTO.Search; | |||
| using GrpcShared.DTO.Track.MultipleTrack; | |||
| using GrpcShared.DTO.Track.SaveTracks; | |||
| using GrpcShared.DTO.Track.SingleTrack; | |||
| using ProtoBuf.Grpc.Configuration; | |||
| using System; | |||
| using System.Collections.Generic; | |||
| @@ -12,5 +15,9 @@ namespace GrpcShared.Interfaces | |||
| public interface ITrackService | |||
| { | |||
| Task<SearchResponse> ListSearchAsync(SearchRequest request); | |||
| Task<SingleTrackResponse> ListSingleTrackAsync(SingleTrackRequest request); | |||
| Task<MultipleTrackResponse> ListMultipleTrackAsync(MultipleTrackRequest request); | |||
| Task SaveTracks(SaveTracksRequest request); | |||
| Task<Microsoft.AspNetCore.Components.Authorization.AuthenticationState> GetAuthenticationStateAsync(); | |||
| } | |||
| } | |||
| @@ -15,10 +15,10 @@ | |||
| <PackageReference Include="protobuf-net.Grpc.AspNetCore" Version="1.0.152" /> | |||
| <PackageReference Include="protobuf-net.Grpc.AspNetCore.Reflection" Version="1.0.152" /> | |||
| <PackageReference Include="System.Configuration.ConfigurationManager" Version="6.0.0" /> | |||
| <PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.22.0" /> | |||
| </ItemGroup> | |||
| <ItemGroup> | |||
| <ProjectReference Include="..\gRPCServer\SpotifyService.csproj" /> | |||
| <ProjectReference Include="..\GrpcShared\GrpcShared.csproj" /> | |||
| <ProjectReference Include="..\NemAnCore\NemAnBlazor.csproj" /> | |||
| </ItemGroup> | |||
| @@ -5,6 +5,7 @@ using ProtoBuf.Grpc.Server; | |||
| using Microsoft.Extensions.Options; | |||
| using GrpcShared.DTO.Auth; | |||
| using SpotifyService.Services; | |||
| using Blazored.SessionStorage; | |||
| var builder = WebApplication.CreateBuilder(args); | |||
| #if DEBUG | |||
| @@ -36,6 +37,7 @@ builder.Services.AddEndpointsApiExplorer(); | |||
| builder.Services.AddGrpc(); | |||
| builder.Services.AddCodeFirstGrpc(); | |||
| builder.Services.AddCodeFirstGrpcReflection(); | |||
| builder.Services.AddBlazoredSessionStorage(); | |||
| //call spotify api | |||
| builder.Services.AddHttpClient(); | |||
| @@ -1,11 +1,17 @@ | |||
| //using IdentityProvider.Protos.AuthService; | |||
| using Blazored.SessionStorage; | |||
| using Grpc.Net.Client; | |||
| using GrpcShared; | |||
| using GrpcShared.DTO; | |||
| using GrpcShared.DTO.Auth; | |||
| using GrpcShared.DTO.User; | |||
| using GrpcShared.Interfaces; | |||
| using Microsoft.Extensions.Options; | |||
| using Microsoft.Net.Http.Headers; | |||
| using Newtonsoft.Json; | |||
| using System.Diagnostics; | |||
| using System.IdentityModel.Tokens.Jwt; | |||
| using System.IO; | |||
| using System.Net.Http.Headers; | |||
| using System.Text; | |||
| using System.Text.Json; | |||
| @@ -17,6 +23,7 @@ namespace IdentityProvider.Services | |||
| private readonly ILogger<AuthService> _logger; | |||
| private readonly CodeRequest _params; | |||
| private readonly IHttpClientFactory _httpClientFactory; | |||
| //private ISessionStorageService _sessionStorageService; | |||
| public AuthService(ILogger<AuthService> logger, IOptions<CodeRequest> options, IHttpClientFactory httpClientFactory) | |||
| { | |||
| _logger = logger; | |||
| @@ -74,5 +81,30 @@ namespace IdentityProvider.Services | |||
| return await Task.FromResult(authParams); | |||
| } | |||
| public async Task<UserInfoResponse> GetUserInfo(TokenMessage tokenM) | |||
| { | |||
| //var des = JsonConvert.DeserializeObject<TokenMessage>(tokenM); | |||
| //var tokenStorage = _sessionStorageService.GetItemAsync<string>("token"); | |||
| //hc.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authToken); | |||
| //var response = hc.GetAsync(userInfoUrl).Result; | |||
| //dynamic userInfo = response.Content.ReadAsAsync().Result; | |||
| //return userInfo; | |||
| var http = _httpClientFactory.CreateClient(); | |||
| http.BaseAddress = new Uri("https://api.spotify.com/v1/me"); | |||
| http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokenM.Token); | |||
| var response = http.GetAsync(http.BaseAddress).Result; | |||
| var userInfo = JsonConvert.DeserializeObject<UserInfoResponse>(await response.Content.ReadAsStringAsync())!; | |||
| return userInfo; | |||
| //http.DefaultRequestHeaders.Add(HeaderNames.Authorization, "Bearer " + token.Token); | |||
| //var response = await http.GetAsync(http.BaseAddress + "me"); | |||
| //var user = JsonConvert.DeserializeObject<UserInfoResponse>(await response.Content.ReadAsStringAsync()); | |||
| //return await Task.FromResult(user!); | |||
| } | |||
| } | |||
| } | |||
| @@ -14,7 +14,7 @@ | |||
| "AuthParams": { | |||
| "ClientId": "83e1d09876b049c4bb1953185a4b3bfb", | |||
| "RedirectURI": "https://localhost:44342/callback", | |||
| "Scope": "user-read-currently-playing user-read-email user-library-modify user-top-read", | |||
| "Scope": "user-read-currently-playing user-read-email user-library-modify user-top-read user-read-private", | |||
| "ClientSecret": "ea752433d0774fad87fab5c1ee788c8d" | |||
| } | |||
| } | |||
| @@ -1,6 +1,11 @@ | |||
| <Router AppAssembly="@typeof(App).Assembly"> | |||
| <CascadingAuthenticationState> | |||
| <Router AppAssembly="@typeof(App).Assembly"> | |||
| <Found Context="routeData"> | |||
| <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" /> | |||
| <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" > | |||
| <Authorizing> | |||
| <text>Please wait, we are authorizint the user.</text> | |||
| </Authorizing> | |||
| </AuthorizeRouteView> | |||
| <FocusOnNavigate RouteData="@routeData" Selector="h1" /> | |||
| </Found> | |||
| <NotFound> | |||
| @@ -10,3 +15,4 @@ | |||
| </LayoutView> | |||
| </NotFound> | |||
| </Router> | |||
| </CascadingAuthenticationState> | |||
| @@ -10,6 +10,7 @@ | |||
| <PackageReference Include="Blazored.SessionStorage" Version="2.2.0" /> | |||
| <PackageReference Include="Grpc.Net.Client" Version="2.47.0" /> | |||
| <PackageReference Include="Grpc.Net.Client.Web" Version="2.47.0" /> | |||
| <PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="6.0.8" /> | |||
| <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="6.0.7" /> | |||
| <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="6.0.7" PrivateAssets="all" /> | |||
| <PackageReference Include="protobuf-net.Grpc" Version="1.0.171" /> | |||
| @@ -1,23 +1,58 @@ | |||
| @page "/search" | |||
| @attribute [Authorize] | |||
| @using Grpc.Core | |||
| @using GrpcShared.DTO | |||
| @using GrpcShared.DTO.Search | |||
| @using GrpcShared.DTO.Track.MultipleTrack | |||
| @using GrpcShared.DTO.Track.SingleTrack | |||
| @using Microsoft.AspNetCore.Authorization | |||
| @using NemAnBlazor.Services | |||
| @using NemAnBlazor.Services.Interfaces | |||
| @using System.Diagnostics | |||
| @*@inject HttpClient Http*@ | |||
| @inject Blazored.SessionStorage.ISessionStorageService sessionStorage | |||
| @inject ITrackClientService SearchService | |||
| @inject IAuthClientService AuthService | |||
| <AuthorizeView> | |||
| <Authorized> | |||
| The user is authorized | |||
| </Authorized> | |||
| <NotAuthorized> | |||
| The User is not authorized | |||
| </NotAuthorized> | |||
| </AuthorizeView> | |||
| <PageTitle>Search</PageTitle> | |||
| <h1>Search</h1> | |||
| <AuthorizeView> | |||
| <Authorized> | |||
| <button class="btn btn-primary" @onclick="Click">Click me</button> | |||
| </Authorized> | |||
| </AuthorizeView> | |||
| @code { | |||
| protected override async Task OnInitializedAsync() | |||
| { | |||
| var token = await sessionStorage.GetItemAsync<string>("token"); | |||
| //MultipleTrackRequest mreq = new() { Ids = new List<string>(){"3JAeYOjyJodI4PRs44lx2l", "6clZa1yrZe7pJrYFUcD9KW"}, Token = token }; | |||
| //MultipleTrackResponse multipleTrackResponse = await SearchService.GetListMultipleTrackAsync(mreq); | |||
| SearchRequest request = new() { Query = "aitch", Type = "track", Token = token }; | |||
| SearchResponse searchResponse = await SearchService.GetListSearchAsync(request); | |||
| //SingleTrackRequest singleTrackRequest = new() { Id = "3JAeYOjyJodI4PRs44lx2l", Token = token }; | |||
| //SingleTrackResponse singleTrackResponse = await SearchService.GetListSingleTrackAsync(singleTrackRequest); | |||
| } | |||
| private async Task Click(){ | |||
| var token = await sessionStorage.GetItemAsync<string>("token"); | |||
| TokenMessage tm = new() { Token = token }; | |||
| SearchRequest request = new() { Query = "aitch", Type = "track", Token = token }; | |||
| SearchResponse searchResponse = await SearchService.GetListSearchAsync(request); | |||
| } | |||
| @@ -8,22 +8,47 @@ | |||
| @inject NavigationManager NavigationManager | |||
| @inject IAuthClientService AuthService | |||
| @inject ITrackClientService SearchService | |||
| @using System.Security.Claims | |||
| <AuthorizeView> | |||
| <Authorized> | |||
| Dobrodosli @context.User.Claims.FirstOrDefault(x => x.Type == "name")?.Value.ToUpper() | |||
| </Authorized> | |||
| <NotAuthorized> | |||
| Nisi autorizovan. | |||
| <button class="btn btn-primary" @onclick="Login">Autorizuj</button> | |||
| </NotAuthorized> | |||
| </AuthorizeView> | |||
| <PageTitle>Index</PageTitle> | |||
| <h1>Pozdrav Diligent!</h1> | |||
| Dobrodošli u našu NemAn aplikaciju. | |||
| @code { | |||
| private string message; | |||
| protected override async Task OnInitializedAsync() | |||
| { | |||
| message = "Cao"; | |||
| } | |||
| private async Task Login() | |||
| { | |||
| //var response = await SearchService.GetListSearchAsync(new GrpcShared.DTO.Search.SearchRequest() { Query="venom", Type = "track"}); | |||
| CodeRequest authParams = await AuthService.GetAuthParams(); | |||
| CodeRequest authParams = await AuthService.GetAuthParams(); | |||
| // await AuthService.GetAccessToken(new CodeResponse{ Code = "hello"}); | |||
| string url = $"https://accounts.spotify.com/en/authorize?client_id={authParams.ClientId}&redirect_uri={authParams.RedirectURI}&response_type={authParams.ResponseType}&scope={authParams.Scope}&show_dialog={authParams.ShowDialog}"; | |||
| NavigationManager.NavigateTo(url); | |||
| } | |||
| } | |||
| @@ -2,6 +2,7 @@ using Blazored.SessionStorage; | |||
| using Grpc.Net.Client; | |||
| using Grpc.Net.Client.Web; | |||
| using Microsoft.AspNetCore.Components; | |||
| using Microsoft.AspNetCore.Components.Authorization; | |||
| using Microsoft.AspNetCore.Components.Web; | |||
| using Microsoft.AspNetCore.Components.WebAssembly.Hosting; | |||
| using NemAnBlazor; | |||
| @@ -21,6 +22,8 @@ builder.Services.AddScoped(_ => | |||
| return channel; | |||
| }); | |||
| builder.Services.AddAuthorizationCore(); | |||
| builder.Services.AddScoped<AuthenticationStateProvider, AuthClientService>(); | |||
| builder.Services.AddScoped<ITrackClientService, TrackClientService>(); | |||
| builder.Services.AddScoped<IAuthClientService, AuthClientService>(); | |||
| @@ -4,16 +4,23 @@ using GrpcShared.Interfaces; | |||
| using NemAnBlazor.Services.Interfaces; | |||
| using ProtoBuf.Grpc.Client; | |||
| using GrpcShared; | |||
| using GrpcShared.DTO.User; | |||
| using GrpcShared.DTO; | |||
| using System.Security.Claims; | |||
| using Microsoft.AspNetCore.Components.Authorization; | |||
| using Blazored.SessionStorage; | |||
| namespace NemAnBlazor.Services | |||
| { | |||
| public class AuthClientService : IAuthClientService | |||
| public class AuthClientService : AuthenticationStateProvider, IAuthClientService | |||
| { | |||
| private IAuthService _serviceClient; | |||
| public AuthClientService(GrpcChannel grpcChannel) | |||
| private ISessionStorageService _sessionStorage; | |||
| public AuthClientService(GrpcChannel grpcChannel, ISessionStorageService sessionStorage) | |||
| { | |||
| _serviceClient = grpcChannel.CreateGrpcService<IAuthService>(); | |||
| _sessionStorage = sessionStorage; | |||
| } | |||
| public async Task<TokenResponse> GetAccessToken(TokenRequest request) | |||
| { | |||
| @@ -24,6 +31,37 @@ namespace NemAnBlazor.Services | |||
| { | |||
| return await _serviceClient.GetAuthParams(); | |||
| } | |||
| public async Task<UserInfoResponse> GetUserInfo(TokenMessage token) | |||
| { | |||
| return await _serviceClient.GetUserInfo(token); | |||
| } | |||
| public override async Task<AuthenticationState> GetAuthenticationStateAsync() | |||
| { | |||
| await Task.Delay(1500); | |||
| string token = await _sessionStorage.GetItemAsync<string>("token"); | |||
| //token = "BQBMgFm6jnFNWWeZEMGIRP_f-ENPid7Kw8JubAyuWAe4JK0S1DPFGlaAdZ_Fey6ePkCnz8-cqC0oyRmrciWUy5ISUTQKDe8PTQn4iBRMYCgM0n4GnS1xAErHJcm4Vpu2TAngk-4vQUOfTQRcedNTfCaHKP4uFJgTlTI7JHGrtB-_EZLnFcZ2OQe31oFQIJ1wM3ZtvwnN"; | |||
| if (token == null) return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity())); | |||
| var userInfo = await _serviceClient.GetUserInfo(new TokenMessage { Token = token }); | |||
| List<Claim> claims = new(); | |||
| claims.Add(new Claim("email", userInfo.email!)); | |||
| claims.Add(new Claim("id", userInfo.id!)); | |||
| claims.Add(new Claim("name", userInfo.display_name!)); | |||
| ClaimsIdentity identity = new(claims, "jwt"); | |||
| //ClaimsIdentity identity = new(); | |||
| ClaimsPrincipal user = new(identity); | |||
| AuthenticationState state = new(user); | |||
| NotifyAuthenticationStateChanged(Task.FromResult(state)); | |||
| return state; | |||
| } | |||
| } | |||
| } | |||
| @@ -1,5 +1,7 @@ | |||
| using GrpcShared; | |||
| using GrpcShared.DTO; | |||
| using GrpcShared.DTO.Auth; | |||
| using GrpcShared.DTO.User; | |||
| namespace NemAnBlazor.Services.Interfaces | |||
| { | |||
| @@ -7,5 +9,6 @@ namespace NemAnBlazor.Services.Interfaces | |||
| { | |||
| Task<TokenResponse> GetAccessToken(TokenRequest tokenRequest); | |||
| Task<CodeRequest> GetAuthParams(); | |||
| Task<UserInfoResponse> GetUserInfo(TokenMessage token); | |||
| } | |||
| } | |||
| @@ -1,9 +1,15 @@ | |||
| using GrpcShared.DTO.Search; | |||
| using GrpcShared.DTO.Track.MultipleTrack; | |||
| using GrpcShared.DTO.Track.SaveTracks; | |||
| using GrpcShared.DTO.Track.SingleTrack; | |||
| namespace NemAnBlazor.Services.Interfaces | |||
| { | |||
| public interface ITrackClientService | |||
| { | |||
| Task<SearchResponse> GetListSearchAsync(SearchRequest request); | |||
| Task<SingleTrackResponse> GetListSingleTrackAsync(SingleTrackRequest request); | |||
| Task<MultipleTrackResponse> GetListMultipleTrackAsync(MultipleTrackRequest request); | |||
| Task PutSaveTracks(SaveTracksRequest request); | |||
| } | |||
| } | |||
| @@ -1,5 +1,8 @@ | |||
| using Grpc.Net.Client; | |||
| using GrpcShared.DTO.Search; | |||
| using GrpcShared.DTO.Track.MultipleTrack; | |||
| using GrpcShared.DTO.Track.SaveTracks; | |||
| using GrpcShared.DTO.Track.SingleTrack; | |||
| using GrpcShared.Interfaces; | |||
| using NemAnBlazor.Services.Interfaces; | |||
| using ProtoBuf.Grpc.Client; | |||
| @@ -15,9 +18,24 @@ namespace NemAnBlazor.Services | |||
| _serviceClient = grpcChannel.CreateGrpcService<ITrackService>(); | |||
| } | |||
| public async Task<SearchResponse> GetListSearchAsync(SearchRequest request) | |||
| { | |||
| return await _serviceClient.ListSearchAsync(request); | |||
| } | |||
| public async Task<SingleTrackResponse> GetListSingleTrackAsync(SingleTrackRequest request) | |||
| { | |||
| return await _serviceClient.ListSingleTrackAsync(request); | |||
| } | |||
| public async Task<MultipleTrackResponse> GetListMultipleTrackAsync(MultipleTrackRequest request) | |||
| { | |||
| return await _serviceClient.ListMultipleTrackAsync(request); | |||
| } | |||
| public async Task PutSaveTracks(SaveTracksRequest request) | |||
| { | |||
| await _serviceClient.SaveTracks(request); | |||
| } | |||
| } | |||
| } | |||
| @@ -1,7 +1,7 @@ | |||
| @inherits LayoutComponentBase | |||
| <div class="page"> | |||
| <div class="sidebar"> | |||
| <div style="background: green ;" class="sidebar"> | |||
| <NavMenu /> | |||
| </div> | |||
| @@ -1,27 +1,27 @@ | |||
| <div class="top-row ps-3 navbar navbar-dark"> | |||
| <div class="container-fluid"> | |||
| <a class="navbar-brand" href="">NemAnCore</a> | |||
| <a class="navbar-brand" href="">Spotify</a> | |||
| <button title="Navigation menu" class="navbar-toggler" @onclick="ToggleNavMenu"> | |||
| <span class="navbar-toggler-icon"></span> | |||
| </button> | |||
| </div> | |||
| </div> | |||
| <div class="@NavMenuCssClass" @onclick="ToggleNavMenu"> | |||
| <nav class="flex-column"> | |||
| <div class="@NavMenuCssClass" @onclick="ToggleNavMenu" > | |||
| <nav class="flex-column" > | |||
| <div class="nav-item px-3"> | |||
| <NavLink class="nav-link" href="" Match="NavLinkMatch.All"> | |||
| <span class="oi oi-home" aria-hidden="true"></span> Home | |||
| </NavLink> | |||
| </div> | |||
| <div class="nav-item px-3"> | |||
| @* <div class="nav-item px-3"> | |||
| <NavLink class="nav-link" href="callback"> | |||
| <span class="oi oi-plus" aria-hidden="true"></span> Counter | |||
| </NavLink> | |||
| </div> | |||
| </div>*@ | |||
| <div class="nav-item px-3"> | |||
| <NavLink class="nav-link" href="fetchdata"> | |||
| <span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data | |||
| <NavLink class="nav-link" href="search"> | |||
| <span class="oi oi-list-rich" aria-hidden="true"></span> Search | |||
| </NavLink> | |||
| </div> | |||
| </nav> | |||
| @@ -8,4 +8,5 @@ | |||
| @using Microsoft.JSInterop | |||
| @using NemAnBlazor | |||
| @using NemAnBlazor.Shared | |||
| @using System.Web | |||
| @using System.Web | |||
| @using Microsoft.AspNetCore.Components.Authorization | |||
| @@ -8,7 +8,7 @@ | |||
| <base href="/" /> | |||
| <link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" /> | |||
| <link href="css/app.css" rel="stylesheet" /> | |||
| <link href="NemAnCore.styles.css" rel="stylesheet" /> | |||
| <link href="NemAnBlazor.styles.css" rel="stylesheet" /> | |||
| </head> | |||
| <body> | |||
| @@ -1,7 +1,11 @@ | |||
| using Microsoft.AspNetCore.Server.Kestrel.Core; | |||
| using NemAnBlazor.Services.Interfaces; | |||
| using NemAnBlazor.Services; | |||
| using ProtoBuf.Grpc.Server; | |||
| using SpotifyService.Services; | |||
| using GrpcShared.Interfaces; | |||
| using Polly; | |||
| using Polly.Extensions.Http; | |||
| var builder = WebApplication.CreateBuilder(args); | |||
| @@ -23,7 +27,54 @@ builder.Services.AddHttpClient("HttpClient", c => | |||
| { | |||
| c.BaseAddress = new Uri(SpotifyService.GLOBALS.SPOTIFYURL); | |||
| c.DefaultRequestHeaders.Add("Accept", SpotifyService.GLOBALS.MEDIATYPE); | |||
| }); | |||
| }) | |||
| .SetHandlerLifetime(TimeSpan.FromMinutes(5)) | |||
| .AddPolicyHandler(GetRetryPolicy()) | |||
| .AddPolicyHandler(GetCircuitBreaker()) | |||
| .AddPolicyHandler(GetBulkheadPolicy(50,200)); | |||
| IAsyncPolicy<HttpResponseMessage> GetBulkheadPolicy(int capacity, int queueLength) | |||
| { | |||
| //As soon as we hit 50 concurrent requests, the policy will add subsequent requests to the pending request queue. | |||
| //Once the pending request queue is full, then the policy will start rejecting any other calls to the service. | |||
| return Policy.BulkheadAsync<HttpResponseMessage>(capacity, queueLength); | |||
| } | |||
| IAsyncPolicy<HttpResponseMessage> GetCircuitBreaker() | |||
| { | |||
| return HttpPolicyExtensions | |||
| .HandleTransientHttpError() | |||
| //the circuit will be cut if 25% of requests fail in a 60 second window, with a minimum of 7 requests in the 60 second window, | |||
| //then the circuit should be cut for 30 seconds: | |||
| .AdvancedCircuitBreakerAsync(0.25, TimeSpan.FromSeconds(60), | |||
| 7, TimeSpan.FromSeconds(30), OnBreak, OnReset, OnHalfOpen); | |||
| } | |||
| void OnHalfOpen() | |||
| { | |||
| Console.WriteLine("Circuit in test mode, one request will be allowed."); | |||
| } | |||
| void OnReset() | |||
| { | |||
| Console.WriteLine("Circuit closed, requests flow normally."); | |||
| } | |||
| void OnBreak(DelegateResult<HttpResponseMessage> result, TimeSpan ts) | |||
| { | |||
| Console.WriteLine("Circuit cut, requests will not flow."); | |||
| } | |||
| IAsyncPolicy<HttpResponseMessage> GetRetryPolicy() | |||
| { | |||
| return HttpPolicyExtensions | |||
| // HttpRequestException, 5XX and 408 | |||
| .HandleTransientHttpError() | |||
| // 404 | |||
| .OrResult(msg => msg.StatusCode == System.Net.HttpStatusCode.NotFound) | |||
| // Retry two times after delay | |||
| .WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(3, retryAttempt))); | |||
| } | |||
| builder.Services.AddControllersWithViews(); | |||
| builder.Services.AddRazorPages(); | |||
| @@ -64,8 +115,9 @@ app.UseGrpcWeb(); | |||
| app.MapRazorPages(); | |||
| app.MapControllers(); | |||
| //app.MapGrpcService<WeatherService>(); | |||
| //app.MapGrpcService<SearchService>().EnableGrpcWeb(); | |||
| //app.MapGrpcService<AuthService>(); | |||
| //app.MapGrpcService<AuthService>().EnableGrpcWeb(); | |||
| app.MapCodeFirstGrpcReflectionService(); | |||
| @@ -1,26 +1,32 @@ | |||
| using Grpc.Core; | |||
| using Google.Rpc; | |||
| using Grpc.Core; | |||
| using GrpcShared; | |||
| using GrpcShared.DTO.Search; | |||
| using GrpcShared.DTO.Track.MultipleTrack; | |||
| using GrpcShared.DTO.Track.SaveTracks; | |||
| using GrpcShared.DTO.Track.SingleTrack; | |||
| using GrpcShared.Interfaces; | |||
| using Microsoft.AspNetCore.Authorization; | |||
| using Microsoft.Net.Http.Headers; | |||
| using NemAnBlazor.Services; | |||
| using Newtonsoft.Json; | |||
| using System.Text; | |||
| using System.Text.Json; | |||
| using System.Web; | |||
| using static GrpcShared.DTO.Search.SearchDetails; | |||
| namespace SpotifyService.Services | |||
| { | |||
| public class TrackService : ITrackService | |||
| { | |||
| private readonly IHttpClientFactory _httpClientFactory; | |||
| public TrackService(IHttpClientFactory httpClientFactory) | |||
| { | |||
| _httpClientFactory = httpClientFactory; | |||
| } | |||
| public async Task<SearchResponse> ListSearchAsync(SearchRequest request) | |||
| { | |||
| var client = _httpClientFactory.CreateClient("HttpClient"); | |||
| client.DefaultRequestHeaders.Add(HeaderNames.Authorization, "Bearer " + request.Token ); | |||
| @@ -36,6 +42,97 @@ namespace SpotifyService.Services | |||
| } | |||
| public async Task<SingleTrackResponse> ListSingleTrackAsync(SingleTrackRequest request) | |||
| { | |||
| var client = _httpClientFactory.CreateClient("HttpClient"); | |||
| client.DefaultRequestHeaders.Add(HeaderNames.Authorization, "Bearer " + request.Token); | |||
| var trackResult = await client.GetAsync($"audio-features/{request.Id}"); | |||
| var responses = JsonConvert.DeserializeObject<SingleTrackResponse>(await trackResult.Content.ReadAsStringAsync())!; | |||
| return new SingleTrackResponse | |||
| { | |||
| Id = responses!.Id, | |||
| Danceability = responses!.Danceability, | |||
| Key = responses!.Key, | |||
| Loudness = responses!.Loudness, | |||
| Mode = responses!.Mode, | |||
| Speechiness = responses!.Speechiness, | |||
| Acousticness = responses!.Acousticness, | |||
| Instrumentalness = responses!.Instrumentalness, | |||
| Liveness = responses!.Liveness, | |||
| Valence = responses!.Valence, | |||
| Tempo = responses!.Tempo, | |||
| Type = responses!.Type, | |||
| Uri = responses!.Uri, | |||
| TrackHref = responses!.TrackHref, | |||
| AnalysisUrl = responses!.AnalysisUrl, | |||
| DurationMs = responses!.DurationMs, | |||
| TimeSignature = responses!.TimeSignature, | |||
| }; | |||
| } | |||
| public async Task<MultipleTrackResponse> ListMultipleTrackAsync(MultipleTrackRequest request) | |||
| { | |||
| var client = _httpClientFactory.CreateClient("HttpClient"); | |||
| client.DefaultRequestHeaders.Add(HeaderNames.Authorization, "Bearer " + request.Token); | |||
| var param = new Dictionary<string, List<string>>(); | |||
| param["ids"] = request.Ids; | |||
| var query = UriUtil(param); | |||
| var trackMultipleResult = await client.GetAsync($"audio-features{query}"); | |||
| var responses = JsonConvert.DeserializeObject<MultipleTrackResponse>(await trackMultipleResult.Content.ReadAsStringAsync())!; | |||
| return new MultipleTrackResponse | |||
| { | |||
| Audio_Features = responses!.Audio_Features | |||
| }; | |||
| } | |||
| public async Task SaveTracks(SaveTracksRequest request) | |||
| { | |||
| var client = _httpClientFactory.CreateClient("HttpClient"); | |||
| client.DefaultRequestHeaders.Add(HeaderNames.Authorization, "Bearer " + request.Token); | |||
| var param = new Dictionary<string, List<string>>(); | |||
| param["ids"] = request.Ids; | |||
| var query = UriUtil(param); | |||
| await client.PutAsync($"me/tracks/{query}", null); | |||
| } | |||
| public static string UriUtil(Dictionary<string, List<string>> param) | |||
| { | |||
| bool startingQuestionMarkAdded = false; | |||
| var sb = new StringBuilder(); | |||
| foreach (var id in param) | |||
| { | |||
| sb.Append(startingQuestionMarkAdded ? '&' : '?'); | |||
| sb.Append(id.Key); | |||
| sb.Append('='); | |||
| foreach (var Xid in id.Value) | |||
| { | |||
| sb.Append(Xid); | |||
| sb.Append("%2C"); | |||
| startingQuestionMarkAdded = true; | |||
| } | |||
| } | |||
| sb.Length = sb.Length - 3; | |||
| return sb.ToString(); | |||
| } | |||
| } | |||
| } | |||
| @@ -7,10 +7,13 @@ | |||
| </PropertyGroup> | |||
| <ItemGroup> | |||
| <PackageReference Include="Google.Api.CommonProtos" Version="2.6.0" /> | |||
| <PackageReference Include="Grpc.AspNetCore" Version="2.40.0" /> | |||
| <PackageReference Include="Grpc.AspNetCore.Web" Version="2.47.0" /> | |||
| <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="6.0.8" /> | |||
| <PackageReference Include="Microsoft.Extensions.Http.Polly" Version="6.0.8" /> | |||
| <PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> | |||
| <PackageReference Include="Polly" Version="7.2.3" /> | |||
| <PackageReference Include="protobuf-net.Grpc" Version="1.0.171" /> | |||
| <PackageReference Include="protobuf-net.Grpc.AspNetCore" Version="1.0.152" /> | |||
| <PackageReference Include="protobuf-net.Grpc.AspNetCore.Reflection" Version="1.0.152" /> | |||
| @@ -19,6 +22,7 @@ | |||
| <ItemGroup> | |||
| <ProjectReference Include="..\GrpcShared\GrpcShared.csproj" /> | |||
| <ProjectReference Include="..\IdentityProvider\IdentityProvider.csproj" /> | |||
| <ProjectReference Include="..\NemAnCore\NemAnBlazor.csproj" /> | |||
| </ItemGroup> | |||