| namespace GrpcShared.DTO.Search | namespace GrpcShared.DTO.Search | ||||
| { | { | ||||
| [ProtoContract] | [ProtoContract] | ||||
| public class SearchRequest : TokenMessage | |||||
| public class SearchRequest : SessionMessage | |||||
| { | { | ||||
| [ProtoMember(1)] | [ProtoMember(1)] |
| using GrpcShared.DTO.Search; | |||||
| using GrpcShared.DTO.TopItem; | |||||
| using GrpcShared.DTO.Track.MultipleTrack; | |||||
| using GrpcShared.DTO.Track.SaveTracks; | |||||
| using GrpcShared.DTO.Track.SingleTrack; | |||||
| using GrpcShared.DTO.TrackByID; | |||||
| using ProtoBuf; | |||||
| using System; | |||||
| using System.Collections.Generic; | |||||
| using System.Linq; | |||||
| using System.Text; | |||||
| using System.Threading.Tasks; | |||||
| namespace GrpcShared.DTO | |||||
| { | |||||
| [ProtoContract] | |||||
| [ProtoInclude(5, typeof(TopItemRequest))] | |||||
| [ProtoInclude(6, typeof(SingleTrackRequest))] | |||||
| [ProtoInclude(7, typeof(SearchRequest))] | |||||
| [ProtoInclude(8, typeof(MultipleTrackRequest))] | |||||
| [ProtoInclude(9, typeof(SaveTracksRequest))] | |||||
| [ProtoInclude(10, typeof(TrackRequest))] | |||||
| public class SessionMessage | |||||
| { | |||||
| [ProtoMember(1)] | |||||
| public string? UserId { get; set; } | |||||
| } | |||||
| } |
| namespace GrpcShared.DTO | namespace GrpcShared.DTO | ||||
| { | { | ||||
| [ProtoInclude(5, typeof(TopItemRequest))] | |||||
| [ProtoInclude(6, typeof(SingleTrackRequest))] | |||||
| [ProtoInclude(7, typeof(SearchRequest))] | |||||
| [ProtoInclude(8,typeof(MultipleTrackRequest))] | |||||
| [ProtoInclude(9,typeof(SaveTracksRequest))] | |||||
| [ProtoInclude(10,typeof(TrackRequest))] | |||||
| [ProtoContract(SkipConstructor = true)] | [ProtoContract(SkipConstructor = true)] | ||||
| public class TokenMessage | public class TokenMessage |
| namespace GrpcShared.DTO.TopItem | namespace GrpcShared.DTO.TopItem | ||||
| { | { | ||||
| [ProtoContract] | [ProtoContract] | ||||
| public class TopItemRequest : TokenMessage | |||||
| public class TopItemRequest : SessionMessage | |||||
| { | { | ||||
| [ProtoMember(1)] | [ProtoMember(1)] |
| namespace GrpcShared.DTO.Track.MultipleTrack | namespace GrpcShared.DTO.Track.MultipleTrack | ||||
| { | { | ||||
| [ProtoContract] | [ProtoContract] | ||||
| public class MultipleTrackRequest:TokenMessage | |||||
| public class MultipleTrackRequest: SessionMessage | |||||
| { | { | ||||
| [ProtoMember(1)] | [ProtoMember(1)] | ||||
| public List<string>? Ids { get; set; } | public List<string>? Ids { get; set; } |
| namespace GrpcShared.DTO.Track.SaveTracks | namespace GrpcShared.DTO.Track.SaveTracks | ||||
| { | { | ||||
| [ProtoContract] | [ProtoContract] | ||||
| public class SaveTracksRequest : TokenMessage | |||||
| public class SaveTracksRequest : SessionMessage | |||||
| { | { | ||||
| [ProtoMember(1)] | [ProtoMember(1)] | ||||
| public List<string>? Ids { get; set; } | public List<string>? Ids { get; set; } |
| namespace GrpcShared.DTO.Track.SingleTrack | namespace GrpcShared.DTO.Track.SingleTrack | ||||
| { | { | ||||
| [ProtoContract] | [ProtoContract] | ||||
| public class SingleTrackRequest : TokenMessage | |||||
| public class SingleTrackRequest : SessionMessage | |||||
| { | { | ||||
| [ProtoMember(1)] | [ProtoMember(1)] | ||||
| public string? Id { get; set; } | public string? Id { get; set; } |
| namespace GrpcShared.DTO.TrackByID | namespace GrpcShared.DTO.TrackByID | ||||
| { | { | ||||
| [ProtoContract] | [ProtoContract] | ||||
| public class TrackRequest:TokenMessage | |||||
| public class TrackRequest: SessionMessage | |||||
| { | { | ||||
| [ProtoMember(1)] | [ProtoMember(1)] | ||||
| [JsonProperty("id")] | [JsonProperty("id")] |
| using GrpcShared.DTO; | using GrpcShared.DTO; | ||||
| using GrpcShared.DTO.Auth; | using GrpcShared.DTO.Auth; | ||||
| using GrpcShared.DTO.Db; | |||||
| using GrpcShared.DTO.User; | using GrpcShared.DTO.User; | ||||
| using ProtoBuf.Grpc.Configuration; | using ProtoBuf.Grpc.Configuration; | ||||
| { | { | ||||
| Task<TokenResponse> GetAccessToken(TokenRequest code); | Task<TokenResponse> GetAccessToken(TokenRequest code); | ||||
| Task<CodeRequest> GetAuthParams(); | Task<CodeRequest> GetAuthParams(); | ||||
| Task<UserInfoResponse> GetUserInfo(TokenMessage token); | |||||
| Task<RefreshTokenResponse> RefreshAccessToken(TokenMessage msg); | |||||
| Task<UserInfoResponse> GetUserInfo(UserResponse token); | |||||
| Task<RefreshTokenResponse> RefreshAccessToken(UserResponse msg); | |||||
| //Task<ClientSecrets> GetClientSecrets(); | //Task<ClientSecrets> GetClientSecrets(); | ||||
| } | } | ||||
| } | } |
| [Service] | [Service] | ||||
| public interface IStatsService | public interface IStatsService | ||||
| { | { | ||||
| Task<CurrentTrackResponse> GetCurrentlyPlayingTrack(TokenMessage token); | |||||
| Task<CurrentTrackResponse> GetCurrentlyPlayingTrack(SessionMessage message); | |||||
| Task<TopItemResponse> GetTopItems(TopItemRequest request); | Task<TopItemResponse> GetTopItems(TopItemRequest request); | ||||
| } | } | ||||
| } | } |
| </ItemGroup> | </ItemGroup> | ||||
| <ItemGroup> | <ItemGroup> | ||||
| <ProjectReference Include="..\gRPCServer\SpotifyService.csproj" /> | |||||
| <ProjectReference Include="..\GrpcShared\GrpcShared.csproj" /> | <ProjectReference Include="..\GrpcShared\GrpcShared.csproj" /> | ||||
| <ProjectReference Include="..\NemAnCore\NemAnBlazor.csproj" /> | <ProjectReference Include="..\NemAnCore\NemAnBlazor.csproj" /> | ||||
| </ItemGroup> | </ItemGroup> |
| { | { | ||||
| public class SpotifyDbConfig | public class SpotifyDbConfig | ||||
| { | { | ||||
| public string ConnectionString { get; set; } = null!; | |||||
| public string DatabaseName { get; set; } = null!; | |||||
| public string UserCollection { get; set; } = null!; | |||||
| public string TracksCollection { get; set; } = null!; | |||||
| public string ConnectionString { get; set; } = "mongodb://127.0.0.1:27017"; | |||||
| public string DatabaseName { get; set; } = "spotifyDb"; | |||||
| public string UserCollection { get; set; } = "Users"; | |||||
| public string TracksCollection { get; set; } = "Tracks"; | |||||
| } | } | ||||
| } | } |
| using GrpcShared; | using GrpcShared; | ||||
| using SpotifyService.Services; | |||||
| using Microsoft.AspNetCore.Server.Kestrel.Core; | using Microsoft.AspNetCore.Server.Kestrel.Core; | ||||
| using ProtoBuf.Grpc.Server; | using ProtoBuf.Grpc.Server; | ||||
| using Microsoft.Extensions.Options; | using Microsoft.Extensions.Options; | ||||
| var builder = WebApplication.CreateBuilder(args); | var builder = WebApplication.CreateBuilder(args); | ||||
| #if DEBUG | #if DEBUG | ||||
| builder.WebHost.ConfigureKestrel(options => | |||||
| { | |||||
| options.ListenLocalhost(5050, o => o.Protocols = | |||||
| HttpProtocols.Http2); | |||||
| options.ListenLocalhost(5051, o => o.Protocols = | |||||
| HttpProtocols.Http1AndHttp2); | |||||
| }); | |||||
| //builder.WebHost.ConfigureKestrel(options => | |||||
| //{ | |||||
| // options.ListenLocalhost(5050, o => o.Protocols = | |||||
| // HttpProtocols.Http2); | |||||
| // options.ListenLocalhost(5051, o => o.Protocols = | |||||
| // HttpProtocols.Http1AndHttp2); | |||||
| //}); | |||||
| #endif | #endif | ||||
| builder.Services.AddHttpClient("HttpClient", c => | |||||
| { | |||||
| c.BaseAddress = new Uri(SpotifyService.GLOBALS.SPOTIFYURL); | |||||
| c.DefaultRequestHeaders.Add("Accept", SpotifyService.GLOBALS.MEDIATYPE); | |||||
| }); | |||||
| builder.Services.AddOptions(); | builder.Services.AddOptions(); | ||||
| builder.Services.AddSingleton<SpotifyDbConfig>(); | |||||
| // Additional configuration is required to successfully run gRPC on macOS. | // Additional configuration is required to successfully run gRPC on macOS. | ||||
| // For instructions on how to configure Kestrel and gRPC clients on macOS, visit https://go.microsoft.com/fwlink/?linkid=2099682 | // For instructions on how to configure Kestrel and gRPC clients on macOS, visit https://go.microsoft.com/fwlink/?linkid=2099682 | ||||
| builder.Services.AddControllersWithViews(); | builder.Services.AddControllersWithViews(); | ||||
| //call spotify api | //call spotify api | ||||
| builder.Services.AddHttpClient(); | builder.Services.AddHttpClient(); | ||||
| builder.Services.Configure<CodeRequest>( | |||||
| builder.Configuration.GetSection("AuthParams")); | |||||
| builder.Services.AddSingleton<SpotifyDbConfig>(); | |||||
| builder.Services.Configure<SpotifyDbConfig>( | builder.Services.Configure<SpotifyDbConfig>( | ||||
| builder.Configuration.GetSection("SpotifyDb")); | builder.Configuration.GetSection("SpotifyDb")); | ||||
| //builder.Services.AddHttpClient("HttpClient", c => | |||||
| //{ | |||||
| // c.BaseAddress = new Uri(builder.Configuration.GetSection("SpotifyConfig:SpotifyURL").Value.ToString()); | |||||
| // c.DefaultRequestHeaders.Add("Accept", builder.Configuration.GetSection("SpotifyConfig:MediaType").Value.ToString()); | |||||
| //}); | |||||
| var app = builder.Build(); | var app = builder.Build(); | ||||
| // Configure the HTTP request pipeline. | // Configure the HTTP request pipeline. | ||||
| app.MapControllers(); | app.MapControllers(); | ||||
| //app.MapGrpcService<WeatherService>(); | //app.MapGrpcService<WeatherService>(); | ||||
| app.MapGrpcService<AuthService>().EnableGrpcWeb(); | |||||
| app.MapGrpcService<TrackService>().EnableGrpcWeb(); | |||||
| app.MapGrpcService<StatsService>().EnableGrpcWeb(); | |||||
| app.MapGrpcService<IdentityService>().EnableGrpcWeb(); | |||||
| //app.MapGrpcService<AuthService>().EnableGrpcWeb(); | |||||
| //app.MapGrpcService<TrackService>().EnableGrpcWeb(); | |||||
| //app.MapGrpcService<StatsService>().EnableGrpcWeb(); | |||||
| //app.MapGrpcService<IdentityService>().EnableGrpcWeb(); | |||||
| app.MapCodeFirstGrpcReflectionService(); | app.MapCodeFirstGrpcReflectionService(); | ||||
| "profiles": { | "profiles": { | ||||
| "IIS Express": { | "IIS Express": { | ||||
| "commandName": "IISExpress", | "commandName": "IISExpress", | ||||
| "launchBrowser": true, | |||||
| "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", | |||||
| "environmentVariables": { | "environmentVariables": { | ||||
| "ASPNETCORE_ENVIRONMENT": "Development" | "ASPNETCORE_ENVIRONMENT": "Development" | ||||
| }, | |||||
| "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}" | |||||
| } | |||||
| }, | }, | ||||
| "IdentityProvider": { | |||||
| "SpotifyService": { | |||||
| "commandName": "Project", | "commandName": "Project", | ||||
| "dotnetRunMessages": "true", | |||||
| "launchBrowser": true, | "launchBrowser": true, | ||||
| "environmentVariables": { | |||||
| "ASPNETCORE_ENVIRONMENT": "Development" | |||||
| }, | |||||
| "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", | ||||
| "applicationUrl": "https://localhost:5001;http://localhost:5000", | "applicationUrl": "https://localhost:5001;http://localhost:5000", | ||||
| "dotnetRunMessages": "true" | |||||
| "environmentVariables": { | |||||
| "ASPNETCORE_ENVIRONMENT": "Development" | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| } | } |
| return new UserResponse | return new UserResponse | ||||
| { | { | ||||
| Id = user.Id, | Id = user.Id, | ||||
| Token = user.Token | |||||
| Token = user.Token, | |||||
| RefreshToken = user.RefreshToken | |||||
| }; | }; | ||||
| else return new UserResponse(); | else return new UserResponse(); | ||||
| } | } |
| "AllowedHosts": "*", | "AllowedHosts": "*", | ||||
| "Kestrel": { | "Kestrel": { | ||||
| "EndpointDefaults": { | "EndpointDefaults": { | ||||
| "Protocols": "Http2" | |||||
| "Protocols": "Http1AndHttp2" | |||||
| } | } | ||||
| }, | }, | ||||
| "AuthParams": { | |||||
| "ClientId": "83e1d09876b049c4bb1953185a4b3bfb", | |||||
| "RedirectURI": "https://localhost:44342/callback", | |||||
| "Scope": "user-read-currently-playing user-read-email user-library-modify user-top-read user-read-private", | |||||
| "ClientSecret": "ea752433d0774fad87fab5c1ee788c8d" | |||||
| }, | |||||
| "SpotifyDb": { | "SpotifyDb": { | ||||
| "ConnectionString": "mongodb://127.0.0.1:27017", | "ConnectionString": "mongodb://127.0.0.1:27017", | ||||
| "DatabaseName": "spotifyDb", | "DatabaseName": "spotifyDb", | ||||
| "UserCollection": "Users", | "UserCollection": "Users", | ||||
| "TracksCollection": "Tracks" | "TracksCollection": "Tracks" | ||||
| } | } | ||||
| //"SpotifyConfig": { | |||||
| // "SpotifyURL": "https://api.spotify.com/v1/", | |||||
| // "MediaType": "application/json" | |||||
| //} | |||||
| } | } |
| @page "/search" | @page "/search" | ||||
| @using Grpc.Core | @using Grpc.Core | ||||
| @using GrpcShared.DTO | @using GrpcShared.DTO | ||||
| @using GrpcShared.DTO.Db | |||||
| @using GrpcShared.DTO.Search | @using GrpcShared.DTO.Search | ||||
| @using GrpcShared.DTO.Track.MultipleTrack | @using GrpcShared.DTO.Track.MultipleTrack | ||||
| @using GrpcShared.DTO.Track.SingleTrack | @using GrpcShared.DTO.Track.SingleTrack | ||||
| var userInfo = await localStorage.GetItemAsync<string>("user_info"); | var userInfo = await localStorage.GetItemAsync<string>("user_info"); | ||||
| var user = await identityService.GetTokenByIdAsync(new GrpcShared.DTO.Db.DbRequestMessage | |||||
| { | |||||
| Id = userInfo | |||||
| }); | |||||
| TokenMessage tokenM = new TokenMessage { Token = user.Token, RefreshToken = user.RefreshToken }; | |||||
| SearchRequest request = new() { Query = "aitch", Type = "track", Token = user.Token }; | |||||
| try | |||||
| { | |||||
| SearchResponse searchResponse = await SearchService.GetListSearchAsync(request); | |||||
| if (searchResponse.ResponseMsg == System.Net.HttpStatusCode.Unauthorized) | |||||
| { | |||||
| string? tempToken = await SpotifyHelper.TryRefreshToken(AuthService, tokenM, user, localStorage, identityService); | |||||
| } | |||||
| } | |||||
| catch (RpcException e) | |||||
| { | |||||
| if (e.StatusCode == StatusCode.Cancelled) | |||||
| { | |||||
| return; | |||||
| } | |||||
| throw; | |||||
| } | |||||
| UserResponse userResponse = await identityService.GetTokenByIdAsync(new DbRequestMessage { Id = userInfo }); | |||||
| //var user = await identityService.GetTokenByIdAsync(new GrpcShared.DTO.Db.DbRequestMessage | |||||
| // { | |||||
| // Id = userInfo | |||||
| // }); | |||||
| //TokenMessage tokenM = new TokenMessage { Token = user.Token, RefreshToken = user.RefreshToken }; | |||||
| SearchRequest request = new() { Query = "aitch", Type = "track", UserId = userInfo }; | |||||
| SearchResponse searchResponse = await SearchService.GetListSearchAsync(request); | |||||
| } | } | ||||
| tokenS = userResponse.Token; | tokenS = userResponse.Token; | ||||
| refreshT = userResponse.RefreshToken; | refreshT = userResponse.RefreshToken; | ||||
| } | } | ||||
| else { | |||||
| else | |||||
| { | |||||
| await localStorage.RemoveItemAsync("user_info"); | await localStorage.RemoveItemAsync("user_info"); | ||||
| NavigationMgr.NavigateTo("/"); | NavigationMgr.NavigateTo("/"); | ||||
| } | } | ||||
| } | } | ||||
| //tokenS = "BQBMgFm6jnFNWWeZEMGIRP_f-ENPid7Kw8JubAyuWAe4JK0S1DPFGlaAdZ_Fey6ePkCnz8-cqC0oyRmrciWUy5ISUTQKDe8PTQn4iBRMYCgM0n4GnS1xAErHJcm4Vpu2TAngk-4vQUOfTQRcedNTfCaHKP4uFJgTlTI7JHGrtB-_EZLnFcZ2OQe31oFQIJ1wM3ZtvwnN"; | //tokenS = "BQBMgFm6jnFNWWeZEMGIRP_f-ENPid7Kw8JubAyuWAe4JK0S1DPFGlaAdZ_Fey6ePkCnz8-cqC0oyRmrciWUy5ISUTQKDe8PTQn4iBRMYCgM0n4GnS1xAErHJcm4Vpu2TAngk-4vQUOfTQRcedNTfCaHKP4uFJgTlTI7JHGrtB-_EZLnFcZ2OQe31oFQIJ1wM3ZtvwnN"; | ||||
| TokenMessage token = new() { Token = tokenS, RefreshToken = refreshT }; | TokenMessage token = new() { Token = tokenS, RefreshToken = refreshT }; | ||||
| UserResponse user = new() { Id = userId, RefreshToken = refreshT }; | UserResponse user = new() { Id = userId, RefreshToken = refreshT }; | ||||
| try | |||||
| { | |||||
| track = await spotifyService.GetCurrentlyPlayingTrack(token); | |||||
| //if token expired, refresh it | |||||
| if (track.ResponseMsg == System.Net.HttpStatusCode.Unauthorized) | |||||
| { | |||||
| string? tempToken = await SpotifyHelper.TryRefreshToken(AuthService, token, user, localStorage, identityService); | |||||
| tokenS = tempToken == null ? tokenS : tempToken; | |||||
| //if refreshed token is null, that means that refresh token was invalid, so you should redirect to login | |||||
| } | |||||
| } | |||||
| catch (RpcException e) | |||||
| { | |||||
| if (e.StatusCode == StatusCode.Cancelled) | |||||
| { | |||||
| return; | |||||
| } | |||||
| throw; | |||||
| } | |||||
| track = await spotifyService.GetCurrentlyPlayingTrack(new SessionMessage { UserId = userId }); | |||||
| //napravi komponentu koja ce da prikazuje sta trenutno slusas i passuj joj parametre | //napravi komponentu koja ce da prikazuje sta trenutno slusas i passuj joj parametre | ||||
| //var trackById = await trackService.GetById(new GrpcShared.DTO.TrackByID.TrackRequest { TrackID = "4fy1A2WBTPX55mUI16TQXa", Token = tokenS }); | //var trackById = await trackService.GetById(new GrpcShared.DTO.TrackByID.TrackRequest { TrackID = "4fy1A2WBTPX55mUI16TQXa", Token = tokenS }); | ||||
| try | |||||
| { | |||||
| var items = await spotifyService.GetTopItems(new GrpcShared.DTO.TopItem.TopItemRequest { Token = tokenS, IsTracks = false, Offset = 5 }); | |||||
| if (items.ResponseMsg == System.Net.HttpStatusCode.Unauthorized) | |||||
| { | |||||
| string? tempToken = await SpotifyHelper.TryRefreshToken(AuthService, token, user, localStorage, identityService); | |||||
| tokenS = tempToken == null ? tokenS : tempToken; | |||||
| } | |||||
| } | |||||
| catch (RpcException e) | |||||
| { | |||||
| if (e.StatusCode == StatusCode.Cancelled) | |||||
| { | |||||
| return; | |||||
| } | |||||
| throw; | |||||
| } | |||||
| var items = await spotifyService.GetTopItems(new GrpcShared.DTO.TopItem.TopItemRequest { UserId = userId, IsTracks = false, Offset = 5 }); | |||||
| await identityService.SaveTrackAsync(new GrpcShared.DTO.Db.SaveTrackRequest | await identityService.SaveTrackAsync(new GrpcShared.DTO.Db.SaveTrackRequest | ||||
| { | { | ||||
| TrackId = track.Item.Id, | TrackId = track.Item.Id, |
| using System.Security.Claims; | using System.Security.Claims; | ||||
| using Microsoft.AspNetCore.Components.Authorization; | using Microsoft.AspNetCore.Components.Authorization; | ||||
| using Blazored.LocalStorage; | using Blazored.LocalStorage; | ||||
| using GrpcShared.DTO.Db; | |||||
| namespace NemAnBlazor.Services | namespace NemAnBlazor.Services | ||||
| { | { | ||||
| return await _serviceClient.GetAuthParams(); | return await _serviceClient.GetAuthParams(); | ||||
| } | } | ||||
| public async Task<UserInfoResponse> GetUserInfo(TokenMessage token) | |||||
| public async Task<UserInfoResponse> GetUserInfo(UserResponse token) | |||||
| { | { | ||||
| return await _serviceClient.GetUserInfo(token); | return await _serviceClient.GetUserInfo(token); | ||||
| } | } | ||||
| //return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity())); | //return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity())); | ||||
| await Task.Delay(500); | await Task.Delay(500); | ||||
| string token = await _localStorage.GetItemAsync<string>("token"); | |||||
| string refreshT = await _localStorage.GetItemAsync<string>("refresh_token"); | |||||
| string userId = await _localStorage.GetItemAsync<string>("user_info"); | |||||
| //token = "BQBMgFm6jnFNWWeZEMGIRP_f-ENPid7Kw8JubAyuWAe4JK0S1DPFGlaAdZ_Fey6ePkCnz8-cqC0oyRmrciWUy5ISUTQKDe8PTQn4iBRMYCgM0n4GnS1xAErHJcm4Vpu2TAngk-4vQUOfTQRcedNTfCaHKP4uFJgTlTI7JHGrtB-_EZLnFcZ2OQe31oFQIJ1wM3ZtvwnN"; | //token = "BQBMgFm6jnFNWWeZEMGIRP_f-ENPid7Kw8JubAyuWAe4JK0S1DPFGlaAdZ_Fey6ePkCnz8-cqC0oyRmrciWUy5ISUTQKDe8PTQn4iBRMYCgM0n4GnS1xAErHJcm4Vpu2TAngk-4vQUOfTQRcedNTfCaHKP4uFJgTlTI7JHGrtB-_EZLnFcZ2OQe31oFQIJ1wM3ZtvwnN"; | ||||
| if (token == null) return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity())); | |||||
| if (userId == null) return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity())); | |||||
| var userInfo = await _serviceClient.GetUserInfo(new TokenMessage { Token = token, RefreshToken = refreshT }); | |||||
| // var userInfo = await _serviceClient.GetUserInfo(new UserResponse { Token = token, RefreshToken = refreshT }); | |||||
| List<Claim> claims = new(); | List<Claim> claims = new(); | ||||
| claims.Add(new Claim("email", userInfo.Email!)); | |||||
| claims.Add(new Claim("id", userInfo.Id!)); | |||||
| claims.Add(new Claim("name", userInfo.DisplayName!)); | |||||
| //claims.Add(new Claim("email", userInfo.Email!)); | |||||
| claims.Add(new Claim("id", userId!)); | |||||
| //claims.Add(new Claim("name", userInfo.DisplayName!)); | |||||
| ClaimsIdentity identity = new(claims, "jwt"); | ClaimsIdentity identity = new(claims, "jwt"); | ||||
| //ClaimsIdentity identity = new(); | //ClaimsIdentity identity = new(); | ||||
| return state; | return state; | ||||
| } | } | ||||
| public async Task<RefreshTokenResponse> RefreshAccessToken(TokenMessage token) | |||||
| public async Task<RefreshTokenResponse> RefreshAccessToken(UserResponse token) | |||||
| { | { | ||||
| return await _serviceClient.RefreshAccessToken(token); | return await _serviceClient.RefreshAccessToken(token); | ||||
| } | } |
| using GrpcShared; | using GrpcShared; | ||||
| using GrpcShared.DTO; | using GrpcShared.DTO; | ||||
| using GrpcShared.DTO.Auth; | using GrpcShared.DTO.Auth; | ||||
| using GrpcShared.DTO.Db; | |||||
| using GrpcShared.DTO.User; | 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); | |||||
| Task<RefreshTokenResponse> RefreshAccessToken(TokenMessage token); | |||||
| Task<UserInfoResponse> GetUserInfo(UserResponse token); | |||||
| Task<RefreshTokenResponse> RefreshAccessToken(UserResponse token); | |||||
| } | } | ||||
| } | } |
| { | { | ||||
| public interface IStatsClientService | public interface IStatsClientService | ||||
| { | { | ||||
| Task<CurrentTrackResponse> GetCurrentlyPlayingTrack(TokenMessage token); | |||||
| Task<CurrentTrackResponse> GetCurrentlyPlayingTrack(SessionMessage message); | |||||
| Task<TopItemResponse> GetTopItems(TopItemRequest request); | Task<TopItemResponse> GetTopItems(TopItemRequest request); | ||||
| } | } | ||||
| } | } |
| { | { | ||||
| _serviceClient = channel.CreateGrpcService<IStatsService>(); | _serviceClient = channel.CreateGrpcService<IStatsService>(); | ||||
| } | } | ||||
| public async Task<CurrentTrackResponse> GetCurrentlyPlayingTrack(TokenMessage token) | |||||
| public async Task<CurrentTrackResponse> GetCurrentlyPlayingTrack(SessionMessage message) | |||||
| { | { | ||||
| return await _serviceClient.GetCurrentlyPlayingTrack(token); | |||||
| return await _serviceClient.GetCurrentlyPlayingTrack(message); | |||||
| } | } | ||||
| public async Task<TopItemResponse> GetTopItems(TopItemRequest request) | public async Task<TopItemResponse> GetTopItems(TopItemRequest request) |
| using Grpc.Net.Client; | |||||
| using Grpc.Core; | |||||
| using Grpc.Net.Client; | |||||
| using GrpcShared.DTO.Db; | |||||
| using GrpcShared.Interfaces; | |||||
| using Microsoft.Net.Http.Headers; | using Microsoft.Net.Http.Headers; | ||||
| using NemAnBlazor.Services.Interfaces; | |||||
| using Newtonsoft.Json; | using Newtonsoft.Json; | ||||
| namespace SpotifyService.HttpUtils | namespace SpotifyService.HttpUtils | ||||
| { | { | ||||
| public static class HttpUtils<T> | |||||
| public static class HttpUtils<T> where T : new() | |||||
| { | { | ||||
| public static async Task<T> GetData(HttpClient client, string url, string token) | |||||
| public static async Task<T> GetData | |||||
| (IHttpClientFactory _httpClientFactory, | |||||
| string url, | |||||
| string userId, | |||||
| IIdentityService identityService, | |||||
| IAuthService authService) | |||||
| { | { | ||||
| //add header | |||||
| client.DefaultRequestHeaders.Add(HeaderNames.Authorization, "Bearer " + token); | |||||
| try | |||||
| { | |||||
| var client = _httpClientFactory.CreateClient("HttpClient"); | |||||
| var userResponse = await identityService.GetTokenByIdAsync(new DbRequestMessage { Id = userId }); | |||||
| //add header | |||||
| client.DefaultRequestHeaders.Add(HeaderNames.Authorization, "Bearer " + userResponse.Token); | |||||
| //get request | |||||
| var req = await client.GetAsync(url); | |||||
| //get request | |||||
| var req = await client.GetAsync(url); | |||||
| //read response | |||||
| var response = JsonConvert.DeserializeObject<T>(await req.Content.ReadAsStringAsync())!; | |||||
| //read response | |||||
| var response = JsonConvert.DeserializeObject<T>(await req.Content.ReadAsStringAsync())!; | |||||
| return response; | |||||
| if (req.StatusCode == System.Net.HttpStatusCode.Unauthorized) | |||||
| { | |||||
| string newToken = await SpotifyHelper.TryRefreshToken(authService, userResponse, identityService); | |||||
| if (newToken != null) | |||||
| { | |||||
| client.DefaultRequestHeaders.Add(HeaderNames.Authorization, "Bearer " + newToken); | |||||
| req = await client.GetAsync(url); | |||||
| response = JsonConvert.DeserializeObject<T>(await req.Content.ReadAsStringAsync())!; | |||||
| } | |||||
| //ako to ne radi to znaci da je refresh token isteko, treba da se refreshuje | |||||
| } | |||||
| return response; | |||||
| } | |||||
| catch (RpcException e) | |||||
| { | |||||
| if (e.StatusCode == StatusCode.Cancelled) | |||||
| { | |||||
| //vrati message sa status kodom? | |||||
| return new T(); | |||||
| } | |||||
| throw; | |||||
| } | |||||
| } | } | ||||
| public static async Task PutData(HttpClient client, string url, string token) | |||||
| public static async Task PutData(HttpClient client, string url, string userId, IIdentityService identityService, IAuthService authService) | |||||
| { | { | ||||
| //add header | |||||
| client.DefaultRequestHeaders.Add(HeaderNames.Authorization, "Bearer " + token); | |||||
| try | |||||
| { | |||||
| var tokenMessage = await identityService.GetTokenByIdAsync(new GrpcShared.DTO.Db.DbRequestMessage { Id = userId }); | |||||
| //add header | |||||
| client.DefaultRequestHeaders.Add(HeaderNames.Authorization, "Bearer " + tokenMessage.Token); | |||||
| //get request | |||||
| var responseMessage = await client.PutAsync(url, null); | |||||
| if(responseMessage.StatusCode == System.Net.HttpStatusCode.Unauthorized) | |||||
| { | |||||
| string newToken = await SpotifyHelper.TryRefreshToken(authService, tokenMessage, identityService); | |||||
| if (newToken != null) | |||||
| { | |||||
| responseMessage = await client.PutAsync(url, null); | |||||
| } | |||||
| } | |||||
| } | |||||
| catch (RpcException e) | |||||
| { | |||||
| if (e.StatusCode == StatusCode.Cancelled) | |||||
| { | |||||
| //vrati message sa status kodom? | |||||
| return; | |||||
| } | |||||
| throw; | |||||
| } | |||||
| //get request | |||||
| await client.PutAsync(url, null); | |||||
| } | } | ||||
| using Blazored.LocalStorage; | |||||
| using GrpcShared.DTO; | |||||
| using GrpcShared.DTO; | |||||
| using GrpcShared.DTO.Db; | using GrpcShared.DTO.Db; | ||||
| using GrpcShared.Interfaces; | |||||
| using NemAnBlazor.Services.Interfaces; | using NemAnBlazor.Services.Interfaces; | ||||
| namespace NemAnBlazor | |||||
| namespace SpotifyService | |||||
| { | { | ||||
| public static class SpotifyHelper | public static class SpotifyHelper | ||||
| { | { | ||||
| public static async Task<string?> TryRefreshToken | public static async Task<string?> TryRefreshToken | ||||
| (IAuthClientService authService, | |||||
| TokenMessage msg,UserResponse user, | |||||
| ILocalStorageService localStorage, | |||||
| IIdentityClientService identityService) | |||||
| (IAuthService authService, | |||||
| UserResponse user, | |||||
| IIdentityService identityService) | |||||
| { | { | ||||
| var refreshResponse = await authService.RefreshAccessToken(msg); | |||||
| var refreshResponse = await authService.RefreshAccessToken(user); | |||||
| if (refreshResponse.AccessToken != null) | if (refreshResponse.AccessToken != null) | ||||
| { | { |
| using Polly; | using Polly; | ||||
| using Polly.Extensions.Http; | using Polly.Extensions.Http; | ||||
| using GrpcShared.DTO.Auth; | using GrpcShared.DTO.Auth; | ||||
| using IdentityProvider.Services; | |||||
| var builder = WebApplication.CreateBuilder(args); | var builder = WebApplication.CreateBuilder(args); | ||||
| #if DEBUG | #if DEBUG | ||||
| /* | |||||
| builder.WebHost.ConfigureKestrel(options => | builder.WebHost.ConfigureKestrel(options => | ||||
| { | { | ||||
| options.ListenLocalhost(5050, o => o.Protocols = | options.ListenLocalhost(5050, o => o.Protocols = | ||||
| options.ListenLocalhost(5051, o => o.Protocols = | options.ListenLocalhost(5051, o => o.Protocols = | ||||
| HttpProtocols.Http1AndHttp2); | HttpProtocols.Http1AndHttp2); | ||||
| }); | }); | ||||
| */ | |||||
| #endif | #endif | ||||
| // Add services to the container. | // Add services to the container. | ||||
| builder.Services.AddGrpc(); | builder.Services.AddGrpc(); | ||||
| builder.Services.AddCodeFirstGrpc(); | builder.Services.AddCodeFirstGrpc(); | ||||
| builder.Services.AddCodeFirstGrpcReflection(); | builder.Services.AddCodeFirstGrpcReflection(); | ||||
| builder.Services.Configure<CodeRequest>(builder.Configuration.GetSection("AuthParams")); | |||||
| builder.Services.AddScoped<IIdentityService, IdentityService>(); | |||||
| builder.Services.AddScoped<IAuthService, AuthService>(); | |||||
| var app = builder.Build(); | var app = builder.Build(); | ||||
| builder.Services.Configure<CodeRequest>(builder.Configuration.GetSection("AuthParams")); | |||||
| app.UseSwagger(); | app.UseSwagger(); | ||||
| app.UseSwaggerUI(); | app.UseSwaggerUI(); | ||||
| app.MapControllers(); | app.MapControllers(); | ||||
| //app.MapGrpcService<AuthService>(); | //app.MapGrpcService<AuthService>(); | ||||
| //app.MapGrpcService<AuthService>().EnableGrpcWeb(); | |||||
| app.MapGrpcService<AuthService>().EnableGrpcWeb(); | |||||
| app.MapGrpcService<TrackService>().EnableGrpcWeb(); | |||||
| app.MapGrpcService<StatsService>().EnableGrpcWeb(); | |||||
| app.MapGrpcService<IdentityService>().EnableGrpcWeb(); | |||||
| app.MapCodeFirstGrpcReflectionService(); | app.MapCodeFirstGrpcReflectionService(); |
| using GrpcShared; | using GrpcShared; | ||||
| using GrpcShared.DTO; | using GrpcShared.DTO; | ||||
| using GrpcShared.DTO.Auth; | using GrpcShared.DTO.Auth; | ||||
| using GrpcShared.DTO.Db; | |||||
| using GrpcShared.DTO.User; | using GrpcShared.DTO.User; | ||||
| using GrpcShared.Interfaces; | using GrpcShared.Interfaces; | ||||
| using Microsoft.Extensions.Options; | using Microsoft.Extensions.Options; | ||||
| return await Task.FromResult(authParams); | return await Task.FromResult(authParams); | ||||
| } | } | ||||
| public async Task<UserInfoResponse> GetUserInfo(TokenMessage tokenM) | |||||
| public async Task<UserInfoResponse> GetUserInfo(UserResponse tokenM) | |||||
| { | { | ||||
| // expired token example "BQBMgFm6jnFNWWeZEMGIRP_f-ENPid7Kw8JubAyuWAe4JK0S1DPFGlaAdZ_Fey6ePkCnz8-cqC0oyRmrciWUy5ISUTQKDe8PTQn4iBRMYCgM0n4GnS1xAErHJcm4Vpu2TAngk-4vQUOfTQRcedNTfCaHKP4uFJgTlTI7JHGrtB-_EZLnFcZ2OQe31oFQIJ1wM3ZtvwnN" | // expired token example "BQBMgFm6jnFNWWeZEMGIRP_f-ENPid7Kw8JubAyuWAe4JK0S1DPFGlaAdZ_Fey6ePkCnz8-cqC0oyRmrciWUy5ISUTQKDe8PTQn4iBRMYCgM0n4GnS1xAErHJcm4Vpu2TAngk-4vQUOfTQRcedNTfCaHKP4uFJgTlTI7JHGrtB-_EZLnFcZ2OQe31oFQIJ1wM3ZtvwnN" | ||||
| } | } | ||||
| public async Task<RefreshTokenResponse> RefreshAccessToken(TokenMessage tokenM) | |||||
| public async Task<RefreshTokenResponse> RefreshAccessToken(UserResponse tokenM) | |||||
| { | { | ||||
| var client = _httpClientFactory.CreateClient("HttpClient"); | var client = _httpClientFactory.CreateClient("HttpClient"); | ||||
| client.BaseAddress = new Uri("https://accounts.spotify.com/api/token"); | client.BaseAddress = new Uri("https://accounts.spotify.com/api/token"); |
| public class StatsService : IStatsService | public class StatsService : IStatsService | ||||
| { | { | ||||
| private readonly IHttpClientFactory _httpClientFactory; | private readonly IHttpClientFactory _httpClientFactory; | ||||
| public StatsService(IHttpClientFactory httpClientFactory) | |||||
| private IIdentityService _identityService; | |||||
| private IAuthService _authService; | |||||
| public StatsService(IHttpClientFactory httpClientFactory, IIdentityService identityService, IAuthService authService) | |||||
| { | { | ||||
| _httpClientFactory = httpClientFactory; | _httpClientFactory = httpClientFactory; | ||||
| _identityService = identityService; | |||||
| _authService = authService; | |||||
| } | } | ||||
| public async Task<CurrentTrackResponse> GetCurrentlyPlayingTrack(TokenMessage token) | |||||
| public async Task<CurrentTrackResponse> GetCurrentlyPlayingTrack(SessionMessage message) | |||||
| { | { | ||||
| var client = _httpClientFactory.CreateClient("HttpClient"); | |||||
| string url = "me/player/currently-playing"; | string url = "me/player/currently-playing"; | ||||
| var response = await HttpUtils<CurrentTrackResponse>.GetData(client, url, token.Token!); | |||||
| var response = await HttpUtils<CurrentTrackResponse>.GetData(_httpClientFactory, url, message.UserId, _identityService, _authService); | |||||
| return response; | return response; | ||||
| public async Task<TopItemResponse> GetTopItems(TopItemRequest request) | public async Task<TopItemResponse> GetTopItems(TopItemRequest request) | ||||
| { | { | ||||
| //https://api.spotify.com/v1/me/top/albums?limit=10&offset=5 | //https://api.spotify.com/v1/me/top/albums?limit=10&offset=5 | ||||
| var client = _httpClientFactory.CreateClient("HttpClient"); | |||||
| //URL PARAMS | //URL PARAMS | ||||
| string url = "me/top/"; | string url = "me/top/"; | ||||
| if (request.Limit == null && request.Offset != null) url += $"?offset={request.Offset}"; | if (request.Limit == null && request.Offset != null) url += $"?offset={request.Offset}"; | ||||
| else url += request.Offset == null ? "" : $"&offset={request.Offset}"; | else url += request.Offset == null ? "" : $"&offset={request.Offset}"; | ||||
| return await HttpUtils<TopItemResponse>.GetData(client, url, request.Token!); | |||||
| return await HttpUtils<TopItemResponse>.GetData(_httpClientFactory, url, request.UserId!, _identityService, _authService); | |||||
| } | } | ||||
| } | } |
| public class TrackService : ITrackService | public class TrackService : ITrackService | ||||
| { | { | ||||
| private readonly IHttpClientFactory _httpClientFactory; | private readonly IHttpClientFactory _httpClientFactory; | ||||
| public TrackService(IHttpClientFactory httpClientFactory) | |||||
| private IIdentityService _identityService; | |||||
| private IAuthService _authService; | |||||
| public TrackService(IHttpClientFactory httpClientFactory, IIdentityService identityService, IAuthService authService) | |||||
| { | { | ||||
| _httpClientFactory = httpClientFactory; | _httpClientFactory = httpClientFactory; | ||||
| _identityService = identityService; | |||||
| _authService = authService; | |||||
| } | } | ||||
| public async Task<SearchResponse> ListSearchAsync(SearchRequest request) | public async Task<SearchResponse> ListSearchAsync(SearchRequest request) | ||||
| { | { | ||||
| var client = _httpClientFactory.CreateClient("HttpClient"); | |||||
| string url = $"search?q={request.Query}&type={request.Type}"; | string url = $"search?q={request.Query}&type={request.Type}"; | ||||
| return await HttpUtils.HttpUtils<SearchResponse>.GetData(client, url, request.Token!); | |||||
| return await HttpUtils.HttpUtils<SearchResponse>.GetData(_httpClientFactory, url, request.UserId!, _identityService, _authService); | |||||
| } | } | ||||
| public async Task<SingleTrackResponse> ListSingleTrackAsync(SingleTrackRequest request) | public async Task<SingleTrackResponse> ListSingleTrackAsync(SingleTrackRequest request) | ||||
| { | { | ||||
| var client = _httpClientFactory.CreateClient("HttpClient"); | |||||
| string url = $"audio-features/{request.Id}"; | string url = $"audio-features/{request.Id}"; | ||||
| return await HttpUtils.HttpUtils<SingleTrackResponse>.GetData(client, url, request.Token!); | |||||
| return await HttpUtils.HttpUtils<SingleTrackResponse>.GetData(_httpClientFactory, url, request.UserId!, _identityService,_authService); | |||||
| } | } | ||||
| public async Task<MultipleTrackResponse> ListMultipleTrackAsync(MultipleTrackRequest request) | public async Task<MultipleTrackResponse> ListMultipleTrackAsync(MultipleTrackRequest request) | ||||
| { | { | ||||
| var client = _httpClientFactory.CreateClient("HttpClient"); | |||||
| var param = new Dictionary<string, List<string>>(); | var param = new Dictionary<string, List<string>>(); | ||||
| param["ids"] = request.Ids!; | param["ids"] = request.Ids!; | ||||
| var query = UriUtil(param); | var query = UriUtil(param); | ||||
| string url = $"audio-features{query}"; | string url = $"audio-features{query}"; | ||||
| return await HttpUtils.HttpUtils<MultipleTrackResponse>.GetData(client,url,request.Token!); | |||||
| return await HttpUtils.HttpUtils<MultipleTrackResponse>.GetData(_httpClientFactory, url, request.UserId, _identityService, _authService); | |||||
| } | } | ||||
| public async Task SaveTracks(SaveTracksRequest request) | public async Task SaveTracks(SaveTracksRequest request) | ||||
| { | { | ||||
| string url = $"me/tracks/{query}"; | string url = $"me/tracks/{query}"; | ||||
| //the response type has nothing to do with the method, it's there so that the method can be called | //the response type has nothing to do with the method, it's there so that the method can be called | ||||
| await HttpUtils.HttpUtils<StatusCodeMessage>.PutData(client, url, request.Token!); | |||||
| await HttpUtils.HttpUtils<StatusCodeMessage>.PutData(client, url, request.UserId!, _identityService, _authService); | |||||
| } | } | ||||
| public static string UriUtil(Dictionary<string, List<string>> param) | public static string UriUtil(Dictionary<string, List<string>> param) | ||||
| } | } | ||||
| public async Task<TrackResponse> GetById(TrackRequest request) | public async Task<TrackResponse> GetById(TrackRequest request) | ||||
| { | { | ||||
| var client = _httpClientFactory.CreateClient("HttpClient"); | |||||
| string url = $"tracks/{request.TrackID}"; | string url = $"tracks/{request.TrackID}"; | ||||
| return await HttpUtils.HttpUtils<TrackResponse>.GetData(client, url, request.Token!); | |||||
| return await HttpUtils.HttpUtils<TrackResponse>.GetData(_httpClientFactory, url, request.UserId!, _identityService, _authService); | |||||
| } | } | ||||
| } | } | ||||
| } | } |
| <ItemGroup> | <ItemGroup> | ||||
| <ProjectReference Include="..\GrpcShared\GrpcShared.csproj" /> | <ProjectReference Include="..\GrpcShared\GrpcShared.csproj" /> | ||||
| <ProjectReference Include="..\IdentityProvider\IdentityProvider.csproj" /> | |||||
| <ProjectReference Include="..\NemAnCore\NemAnBlazor.csproj" /> | <ProjectReference Include="..\NemAnCore\NemAnBlazor.csproj" /> | ||||
| </ItemGroup> | </ItemGroup> | ||||