| 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; } | |||||
| } | |||||
| } |
| 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; } | |||||
| } | |||||
| } |
| 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; } | |||||
| } | |||||
| } |
| 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; } | |||||
| } | |||||
| } |
| 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; } | |||||
| } | |||||
| } |
| 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; } | |||||
| } | |||||
| } |
| 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; } | |||||
| } | |||||
| } |
| using System; | |||||
| namespace Shared | |||||
| { | |||||
| public static class GLOBALS | |||||
| { | |||||
| public const String SPOTIFYURL = "https://api.spotify.com/v1/"; | |||||
| public const String MEDIATYPE = "application/json"; | |||||
| } | |||||
| } |
| </PropertyGroup> | </PropertyGroup> | ||||
| <ItemGroup> | <ItemGroup> | ||||
| <PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" /> | |||||
| <PackageReference Include="Microsoft.Extensions.Configuration" Version="6.0.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"> | <PackageReference Include="protobuf-net.BuildTools" Version="3.1.17"> | ||||
| <PrivateAssets>all</PrivateAssets> | <PrivateAssets>all</PrivateAssets> | ||||
| <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> |
| using GrpcShared.DTO.Auth; | |||||
| using GrpcShared.DTO; | |||||
| using GrpcShared.DTO.Auth; | |||||
| using GrpcShared.DTO.User; | |||||
| using ProtoBuf.Grpc.Configuration; | using ProtoBuf.Grpc.Configuration; | ||||
| namespace GrpcShared.Interfaces | namespace GrpcShared.Interfaces | ||||
| { | { | ||||
| Task<TokenResponse> GetAccessToken(TokenRequest code); | Task<TokenResponse> GetAccessToken(TokenRequest code); | ||||
| Task<CodeRequest> GetAuthParams(); | Task<CodeRequest> GetAuthParams(); | ||||
| Task<UserInfoResponse> GetUserInfo(TokenMessage token); | |||||
| //Task<ClientSecrets> GetClientSecrets(); | //Task<ClientSecrets> GetClientSecrets(); | ||||
| } | } | ||||
| } | } |
| using GrpcShared.DTO.Search; | using GrpcShared.DTO.Search; | ||||
| using GrpcShared.DTO.Track.MultipleTrack; | |||||
| using GrpcShared.DTO.Track.SaveTracks; | |||||
| using GrpcShared.DTO.Track.SingleTrack; | |||||
| using ProtoBuf.Grpc.Configuration; | using ProtoBuf.Grpc.Configuration; | ||||
| using System; | using System; | ||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| public interface ITrackService | public interface ITrackService | ||||
| { | { | ||||
| Task<SearchResponse> ListSearchAsync(SearchRequest request); | Task<SearchResponse> ListSearchAsync(SearchRequest request); | ||||
| Task<SingleTrackResponse> ListSingleTrackAsync(SingleTrackRequest request); | |||||
| Task<MultipleTrackResponse> ListMultipleTrackAsync(MultipleTrackRequest request); | |||||
| Task SaveTracks(SaveTracksRequest request); | |||||
| } | } | ||||
| } | } |
| <PackageReference Include="protobuf-net.Grpc.AspNetCore" Version="1.0.152" /> | <PackageReference Include="protobuf-net.Grpc.AspNetCore" Version="1.0.152" /> | ||||
| <PackageReference Include="protobuf-net.Grpc.AspNetCore.Reflection" 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.Configuration.ConfigurationManager" Version="6.0.0" /> | ||||
| <PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.22.0" /> | |||||
| </ItemGroup> | </ItemGroup> | ||||
| <ItemGroup> | <ItemGroup> |
| using Microsoft.Extensions.Options; | using Microsoft.Extensions.Options; | ||||
| using GrpcShared.DTO.Auth; | using GrpcShared.DTO.Auth; | ||||
| using SpotifyService.Services; | using SpotifyService.Services; | ||||
| using Blazored.SessionStorage; | |||||
| var builder = WebApplication.CreateBuilder(args); | var builder = WebApplication.CreateBuilder(args); | ||||
| #if DEBUG | #if DEBUG | ||||
| builder.Services.AddGrpc(); | builder.Services.AddGrpc(); | ||||
| builder.Services.AddCodeFirstGrpc(); | builder.Services.AddCodeFirstGrpc(); | ||||
| builder.Services.AddCodeFirstGrpcReflection(); | builder.Services.AddCodeFirstGrpcReflection(); | ||||
| builder.Services.AddBlazoredSessionStorage(); | |||||
| //call spotify api | //call spotify api | ||||
| builder.Services.AddHttpClient(); | builder.Services.AddHttpClient(); | ||||
| //using IdentityProvider.Protos.AuthService; | //using IdentityProvider.Protos.AuthService; | ||||
| using Blazored.SessionStorage; | |||||
| using Grpc.Net.Client; | using Grpc.Net.Client; | ||||
| using GrpcShared; | using GrpcShared; | ||||
| using GrpcShared.DTO; | |||||
| using GrpcShared.DTO.Auth; | using GrpcShared.DTO.Auth; | ||||
| using GrpcShared.DTO.User; | |||||
| using GrpcShared.Interfaces; | using GrpcShared.Interfaces; | ||||
| using Microsoft.Extensions.Options; | using Microsoft.Extensions.Options; | ||||
| using Microsoft.Net.Http.Headers; | using Microsoft.Net.Http.Headers; | ||||
| using Newtonsoft.Json; | using Newtonsoft.Json; | ||||
| using System.Diagnostics; | |||||
| using System.IdentityModel.Tokens.Jwt; | |||||
| using System.IO; | |||||
| using System.Net.Http.Headers; | using System.Net.Http.Headers; | ||||
| using System.Text; | using System.Text; | ||||
| using System.Text.Json; | using System.Text.Json; | ||||
| private readonly ILogger<AuthService> _logger; | private readonly ILogger<AuthService> _logger; | ||||
| private readonly CodeRequest _params; | private readonly CodeRequest _params; | ||||
| private readonly IHttpClientFactory _httpClientFactory; | private readonly IHttpClientFactory _httpClientFactory; | ||||
| //private ISessionStorageService _sessionStorageService; | |||||
| public AuthService(ILogger<AuthService> logger, IOptions<CodeRequest> options, IHttpClientFactory httpClientFactory) | public AuthService(ILogger<AuthService> logger, IOptions<CodeRequest> options, IHttpClientFactory httpClientFactory) | ||||
| { | { | ||||
| _logger = logger; | _logger = logger; | ||||
| return await Task.FromResult(authParams); | 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!); | |||||
| } | |||||
| } | } | ||||
| } | } |
| "AuthParams": { | "AuthParams": { | ||||
| "ClientId": "83e1d09876b049c4bb1953185a4b3bfb", | "ClientId": "83e1d09876b049c4bb1953185a4b3bfb", | ||||
| "RedirectURI": "https://localhost:44342/callback", | "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" | "ClientSecret": "ea752433d0774fad87fab5c1ee788c8d" | ||||
| } | } | ||||
| } | } |
| <CascadingAuthenticationState> | <CascadingAuthenticationState> | ||||
| <Router AppAssembly="@typeof(App).Assembly"> | <Router AppAssembly="@typeof(App).Assembly"> | ||||
| <Found Context="routeData"> | |||||
| <Found Context="routeData"> | |||||
| <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" > | <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" > | ||||
| <NotAuthorized>Sorry nisi autorizovan</NotAuthorized> | |||||
| <Authorizing> | |||||
| <text>Please wait, we are authorizint the user.</text> | |||||
| </Authorizing> | |||||
| </AuthorizeRouteView> | </AuthorizeRouteView> | ||||
| <FocusOnNavigate RouteData="@routeData" Selector="h1" /> | <FocusOnNavigate RouteData="@routeData" Selector="h1" /> | ||||
| </Found> | </Found> | ||||
| </LayoutView> | </LayoutView> | ||||
| </NotFound> | </NotFound> | ||||
| </Router> | </Router> | ||||
| </CascadingAuthenticationState> | |||||
| </CascadingAuthenticationState> |
| @page "/search" | @page "/search" | ||||
| @attribute [Authorize] | |||||
| @using Grpc.Core | |||||
| @using GrpcShared.DTO | |||||
| @using GrpcShared.DTO.Search | @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 NemAnBlazor.Services.Interfaces | ||||
| @using System.Diagnostics | |||||
| @*@inject HttpClient Http*@ | @*@inject HttpClient Http*@ | ||||
| @inject Blazored.SessionStorage.ISessionStorageService sessionStorage | @inject Blazored.SessionStorage.ISessionStorageService sessionStorage | ||||
| @inject ITrackClientService SearchService | @inject ITrackClientService SearchService | ||||
| @inject IAuthClientService AuthService | |||||
| <AuthorizeView> | |||||
| <Authorized> | |||||
| The user is authorized | |||||
| </Authorized> | |||||
| <NotAuthorized> | |||||
| The User is not authorized | |||||
| </NotAuthorized> | |||||
| </AuthorizeView> | |||||
| <PageTitle>Search</PageTitle> | <PageTitle>Search</PageTitle> | ||||
| <h1>Search</h1> | <h1>Search</h1> | ||||
| <AuthorizeView> | |||||
| <Authorized> | |||||
| <button class="btn btn-primary" @onclick="Click">Click me</button> | |||||
| </Authorized> | |||||
| </AuthorizeView> | |||||
| @code { | @code { | ||||
| protected override async Task OnInitializedAsync() | 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); | |||||
| } | } | ||||
| @inject NavigationManager NavigationManager | @inject NavigationManager NavigationManager | ||||
| @inject IAuthClientService AuthService | @inject IAuthClientService AuthService | ||||
| @inject ITrackClientService SearchService | @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> | <PageTitle>Index</PageTitle> | ||||
| <h1>Pozdrav Diligent!</h1> | <h1>Pozdrav Diligent!</h1> | ||||
| Dobrodošli u našu NemAn aplikaciju. | Dobrodošli u našu NemAn aplikaciju. | ||||
| @code { | @code { | ||||
| private string message; | |||||
| protected override async Task OnInitializedAsync() | 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"}); | //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"}); | // 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}"; | 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); | NavigationManager.NavigateTo(url); | ||||
| } | } | ||||
| } | } |
| using Grpc.Net.Client; | using Grpc.Net.Client; | ||||
| using Grpc.Net.Client.Web; | using Grpc.Net.Client.Web; | ||||
| using Microsoft.AspNetCore.Components; | using Microsoft.AspNetCore.Components; | ||||
| using Microsoft.AspNetCore.Components.Authorization; | |||||
| using Microsoft.AspNetCore.Components.Web; | using Microsoft.AspNetCore.Components.Web; | ||||
| using Microsoft.AspNetCore.Components.WebAssembly.Hosting; | using Microsoft.AspNetCore.Components.WebAssembly.Hosting; | ||||
| using NemAnBlazor; | using NemAnBlazor; | ||||
| return channel; | return channel; | ||||
| }); | }); | ||||
| builder.Services.AddAuthorizationCore(); | |||||
| builder.Services.AddScoped<AuthenticationStateProvider, AuthClientService>(); | |||||
| builder.Services.AddScoped<ITrackClientService, TrackClientService>(); | builder.Services.AddScoped<ITrackClientService, TrackClientService>(); | ||||
| builder.Services.AddScoped<IAuthClientService, AuthClientService>(); | builder.Services.AddScoped<IAuthClientService, AuthClientService>(); |
| using NemAnBlazor.Services.Interfaces; | using NemAnBlazor.Services.Interfaces; | ||||
| using ProtoBuf.Grpc.Client; | using ProtoBuf.Grpc.Client; | ||||
| using GrpcShared; | using GrpcShared; | ||||
| using GrpcShared.DTO.User; | |||||
| using GrpcShared.DTO; | |||||
| using System.Security.Claims; | |||||
| using Microsoft.AspNetCore.Components.Authorization; | |||||
| using Blazored.SessionStorage; | |||||
| namespace NemAnBlazor.Services | namespace NemAnBlazor.Services | ||||
| { | { | ||||
| public class AuthClientService : IAuthClientService | |||||
| public class AuthClientService : AuthenticationStateProvider, IAuthClientService | |||||
| { | { | ||||
| private IAuthService _serviceClient; | private IAuthService _serviceClient; | ||||
| public AuthClientService(GrpcChannel grpcChannel) | |||||
| private ISessionStorageService _sessionStorage; | |||||
| public AuthClientService(GrpcChannel grpcChannel, ISessionStorageService sessionStorage) | |||||
| { | { | ||||
| _serviceClient = grpcChannel.CreateGrpcService<IAuthService>(); | _serviceClient = grpcChannel.CreateGrpcService<IAuthService>(); | ||||
| _sessionStorage = sessionStorage; | |||||
| } | } | ||||
| public async Task<TokenResponse> GetAccessToken(TokenRequest request) | public async Task<TokenResponse> GetAccessToken(TokenRequest request) | ||||
| { | { | ||||
| { | { | ||||
| return await _serviceClient.GetAuthParams(); | 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; | |||||
| } | |||||
| } | } | ||||
| } | } |
| using GrpcShared; | using GrpcShared; | ||||
| using GrpcShared.DTO; | |||||
| using GrpcShared.DTO.Auth; | using GrpcShared.DTO.Auth; | ||||
| using GrpcShared.DTO.User; | |||||
| namespace NemAnBlazor.Services.Interfaces | namespace NemAnBlazor.Services.Interfaces | ||||
| { | { | ||||
| { | { | ||||
| Task<TokenResponse> GetAccessToken(TokenRequest tokenRequest); | Task<TokenResponse> GetAccessToken(TokenRequest tokenRequest); | ||||
| Task<CodeRequest> GetAuthParams(); | Task<CodeRequest> GetAuthParams(); | ||||
| Task<UserInfoResponse> GetUserInfo(TokenMessage token); | |||||
| } | } | ||||
| } | } |
| using GrpcShared.DTO.Search; | using GrpcShared.DTO.Search; | ||||
| using GrpcShared.DTO.Track.MultipleTrack; | |||||
| using GrpcShared.DTO.Track.SaveTracks; | |||||
| using GrpcShared.DTO.Track.SingleTrack; | |||||
| namespace NemAnBlazor.Services.Interfaces | namespace NemAnBlazor.Services.Interfaces | ||||
| { | { | ||||
| public interface ITrackClientService | public interface ITrackClientService | ||||
| { | { | ||||
| Task<SearchResponse> GetListSearchAsync(SearchRequest request); | Task<SearchResponse> GetListSearchAsync(SearchRequest request); | ||||
| Task<SingleTrackResponse> GetListSingleTrackAsync(SingleTrackRequest request); | |||||
| Task<MultipleTrackResponse> GetListMultipleTrackAsync(MultipleTrackRequest request); | |||||
| Task PutSaveTracks(SaveTracksRequest request); | |||||
| } | } | ||||
| } | } |
| using Grpc.Net.Client; | using Grpc.Net.Client; | ||||
| using GrpcShared.DTO.Search; | using GrpcShared.DTO.Search; | ||||
| using GrpcShared.DTO.Track.MultipleTrack; | |||||
| using GrpcShared.DTO.Track.SaveTracks; | |||||
| using GrpcShared.DTO.Track.SingleTrack; | |||||
| using GrpcShared.Interfaces; | using GrpcShared.Interfaces; | ||||
| using NemAnBlazor.Services.Interfaces; | using NemAnBlazor.Services.Interfaces; | ||||
| using ProtoBuf.Grpc.Client; | using ProtoBuf.Grpc.Client; | ||||
| _serviceClient = grpcChannel.CreateGrpcService<ITrackService>(); | _serviceClient = grpcChannel.CreateGrpcService<ITrackService>(); | ||||
| } | } | ||||
| public async Task<SearchResponse> GetListSearchAsync(SearchRequest request) | public async Task<SearchResponse> GetListSearchAsync(SearchRequest request) | ||||
| { | { | ||||
| return await _serviceClient.ListSearchAsync(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); | |||||
| } | |||||
| } | } | ||||
| } | } |
| @inherits LayoutComponentBase | @inherits LayoutComponentBase | ||||
| <div class="page"> | <div class="page"> | ||||
| <div class="sidebar"> | |||||
| <div style="background: green ;" class="sidebar"> | |||||
| <NavMenu /> | <NavMenu /> | ||||
| </div> | </div> | ||||
| <div class="top-row ps-3 navbar navbar-dark"> | <div class="top-row ps-3 navbar navbar-dark"> | ||||
| <div class="container-fluid"> | <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"> | <button title="Navigation menu" class="navbar-toggler" @onclick="ToggleNavMenu"> | ||||
| <span class="navbar-toggler-icon"></span> | <span class="navbar-toggler-icon"></span> | ||||
| </button> | </button> | ||||
| </div> | </div> | ||||
| </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"> | <div class="nav-item px-3"> | ||||
| <NavLink class="nav-link" href="" Match="NavLinkMatch.All"> | <NavLink class="nav-link" href="" Match="NavLinkMatch.All"> | ||||
| <span class="oi oi-home" aria-hidden="true"></span> Home | <span class="oi oi-home" aria-hidden="true"></span> Home | ||||
| </NavLink> | </NavLink> | ||||
| </div> | </div> | ||||
| <div class="nav-item px-3"> | |||||
| @* <div class="nav-item px-3"> | |||||
| <NavLink class="nav-link" href="callback"> | <NavLink class="nav-link" href="callback"> | ||||
| <span class="oi oi-plus" aria-hidden="true"></span> Counter | <span class="oi oi-plus" aria-hidden="true"></span> Counter | ||||
| </NavLink> | </NavLink> | ||||
| </div> | |||||
| </div>*@ | |||||
| <div class="nav-item px-3"> | <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> | </NavLink> | ||||
| </div> | </div> | ||||
| </nav> | </nav> |
| @using NemAnBlazor | @using NemAnBlazor | ||||
| @using NemAnBlazor.Shared | @using NemAnBlazor.Shared | ||||
| @using System.Web | @using System.Web | ||||
| @using Microsoft.AspNetCore.Components.Authorization | |||||
| @using Microsoft.AspNetCore.Components.Authorization |
| <base href="/" /> | <base href="/" /> | ||||
| <link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" /> | <link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" /> | ||||
| <link href="css/app.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> | </head> | ||||
| <body> | <body> |
| using Microsoft.AspNetCore.Server.Kestrel.Core; | using Microsoft.AspNetCore.Server.Kestrel.Core; | ||||
| using NemAnBlazor.Services.Interfaces; | |||||
| using NemAnBlazor.Services; | |||||
| using ProtoBuf.Grpc.Server; | using ProtoBuf.Grpc.Server; | ||||
| using SpotifyService.Services; | using SpotifyService.Services; | ||||
| using GrpcShared.Interfaces; | |||||
| using Polly; | |||||
| using Polly.Extensions.Http; | |||||
| var builder = WebApplication.CreateBuilder(args); | var builder = WebApplication.CreateBuilder(args); | ||||
| { | { | ||||
| c.BaseAddress = new Uri(SpotifyService.GLOBALS.SPOTIFYURL); | c.BaseAddress = new Uri(SpotifyService.GLOBALS.SPOTIFYURL); | ||||
| c.DefaultRequestHeaders.Add("Accept", SpotifyService.GLOBALS.MEDIATYPE); | 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.AddControllersWithViews(); | ||||
| builder.Services.AddRazorPages(); | builder.Services.AddRazorPages(); | ||||
| app.MapRazorPages(); | app.MapRazorPages(); | ||||
| app.MapControllers(); | app.MapControllers(); | ||||
| //app.MapGrpcService<WeatherService>(); | |||||
| //app.MapGrpcService<SearchService>().EnableGrpcWeb(); | |||||
| //app.MapGrpcService<AuthService>(); | |||||
| //app.MapGrpcService<AuthService>().EnableGrpcWeb(); | |||||
| app.MapCodeFirstGrpcReflectionService(); | app.MapCodeFirstGrpcReflectionService(); | ||||
| using Grpc.Core; | |||||
| using Google.Rpc; | |||||
| using Grpc.Core; | |||||
| using GrpcShared; | using GrpcShared; | ||||
| using GrpcShared.DTO.Search; | using GrpcShared.DTO.Search; | ||||
| using GrpcShared.DTO.Track.MultipleTrack; | |||||
| using GrpcShared.DTO.Track.SaveTracks; | |||||
| using GrpcShared.DTO.Track.SingleTrack; | |||||
| using GrpcShared.Interfaces; | using GrpcShared.Interfaces; | ||||
| using Microsoft.AspNetCore.Authorization; | |||||
| using Microsoft.Net.Http.Headers; | using Microsoft.Net.Http.Headers; | ||||
| using NemAnBlazor.Services; | |||||
| using Newtonsoft.Json; | using Newtonsoft.Json; | ||||
| using System.Text; | using System.Text; | ||||
| using System.Text.Json; | using System.Text.Json; | ||||
| using System.Web; | |||||
| using static GrpcShared.DTO.Search.SearchDetails; | |||||
| namespace SpotifyService.Services | namespace SpotifyService.Services | ||||
| { | { | ||||
| public class TrackService : ITrackService | public class TrackService : ITrackService | ||||
| { | { | ||||
| private readonly IHttpClientFactory _httpClientFactory; | private readonly IHttpClientFactory _httpClientFactory; | ||||
| public TrackService(IHttpClientFactory httpClientFactory) | public TrackService(IHttpClientFactory httpClientFactory) | ||||
| { | { | ||||
| _httpClientFactory = httpClientFactory; | _httpClientFactory = httpClientFactory; | ||||
| } | } | ||||
| public async Task<SearchResponse> ListSearchAsync(SearchRequest request) | public async Task<SearchResponse> ListSearchAsync(SearchRequest request) | ||||
| { | { | ||||
| var client = _httpClientFactory.CreateClient("HttpClient"); | var client = _httpClientFactory.CreateClient("HttpClient"); | ||||
| client.DefaultRequestHeaders.Add(HeaderNames.Authorization, "Bearer " + request.Token ); | client.DefaultRequestHeaders.Add(HeaderNames.Authorization, "Bearer " + request.Token ); | ||||
| } | } | ||||
| 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(); | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| </PropertyGroup> | </PropertyGroup> | ||||
| <ItemGroup> | <ItemGroup> | ||||
| <PackageReference Include="Google.Api.CommonProtos" Version="2.6.0" /> | |||||
| <PackageReference Include="Grpc.AspNetCore" Version="2.40.0" /> | <PackageReference Include="Grpc.AspNetCore" Version="2.40.0" /> | ||||
| <PackageReference Include="Grpc.AspNetCore.Web" Version="2.47.0" /> | <PackageReference Include="Grpc.AspNetCore.Web" Version="2.47.0" /> | ||||
| <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="6.0.8" /> | <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="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" Version="1.0.171" /> | ||||
| <PackageReference Include="protobuf-net.Grpc.AspNetCore" Version="1.0.152" /> | <PackageReference Include="protobuf-net.Grpc.AspNetCore" Version="1.0.152" /> | ||||
| <PackageReference Include="protobuf-net.Grpc.AspNetCore.Reflection" Version="1.0.152" /> | <PackageReference Include="protobuf-net.Grpc.AspNetCore.Reflection" Version="1.0.152" /> |