| @@ -0,0 +1,28 @@ | |||
| using ProtoBuf; | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.ComponentModel; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace GrpcShared.DTO.Auth | |||
| { | |||
| [ProtoContract] | |||
| public class CodeRequest | |||
| { | |||
| [ProtoMember(1)] | |||
| public string? ClientId { get; set; } | |||
| [ProtoMember(2)] | |||
| public string? ClientSecret { get; set; } | |||
| [ProtoMember(3)] | |||
| public string? RedirectURI { get; set; } | |||
| [ProtoMember(4)] | |||
| public string ResponseType { get; set; } = "code"; | |||
| [ProtoMember(5)] | |||
| public string? Scope { get; set; } | |||
| [ProtoMember(6)] | |||
| [DefaultValue(true)] | |||
| public bool ShowDialog { get; set; } = true; | |||
| } | |||
| } | |||
| @@ -0,0 +1,22 @@ | |||
| using Newtonsoft.Json; | |||
| using ProtoBuf; | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace GrpcShared.DTO.Auth | |||
| { | |||
| [ProtoContract] | |||
| public class RefreshTokenResponse : StatusCodeMessage | |||
| { | |||
| [ProtoMember(1)] | |||
| [JsonProperty("access_token")] | |||
| public string? AccessToken { get; set; } | |||
| [ProtoMember(2)] | |||
| [JsonProperty("expires_in")] | |||
| public int? Expiration { get; set; } | |||
| } | |||
| } | |||
| @@ -0,0 +1,26 @@ | |||
| using Newtonsoft.Json; | |||
| using ProtoBuf; | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace GrpcShared.DTO.Auth | |||
| { | |||
| [ProtoContract] | |||
| public class TokenRequest | |||
| { | |||
| [ProtoMember(1)] | |||
| [JsonProperty("grant_type")] | |||
| public string GrantType { get; set; } = "authorization_code"; | |||
| [ProtoMember(2)] | |||
| [JsonProperty("code")] | |||
| public string? Code { get; set; } | |||
| [ProtoMember(3)] | |||
| [JsonProperty("redirect_uri")] | |||
| public string? RedirectUri { get; set; } | |||
| } | |||
| } | |||
| @@ -0,0 +1,24 @@ | |||
| using Newtonsoft.Json; | |||
| using ProtoBuf; | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace GrpcShared.DTO.Auth | |||
| { | |||
| [ProtoContract] | |||
| public class TokenResponse | |||
| { | |||
| [ProtoMember(1)] | |||
| [JsonProperty("access_token")] | |||
| public string? AccessToken { get; set; } | |||
| [ProtoMember(2)] | |||
| [JsonProperty("refresh_token")] | |||
| public string? RefreshToken{ get; set; } | |||
| [ProtoMember(3)] | |||
| [JsonProperty("expires_in")] | |||
| public int? Expiration { get; set; } | |||
| } | |||
| } | |||
| @@ -0,0 +1,131 @@ | |||
| using ProtoBuf; | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace GrpcShared.DTO.Search | |||
| { | |||
| [ProtoContract] | |||
| public class SearchDetails | |||
| { | |||
| [ProtoContract] | |||
| public partial class SearchContracts | |||
| { | |||
| [ProtoMember(1)] | |||
| public Tracks? Tracks { get; set; } | |||
| } | |||
| [ProtoContract] | |||
| public partial class Tracks | |||
| { | |||
| [ProtoMember(1)] | |||
| public Uri? Href { get; set; } | |||
| [ProtoMember(2)] | |||
| public Item[]? Items { get; set; } | |||
| } | |||
| [ProtoContract] | |||
| public partial class Item | |||
| { | |||
| [ProtoMember(1)] | |||
| public Album? Album { get; set; } | |||
| [ProtoMember(2)] | |||
| public Artist[]? Artists { get; set; } | |||
| [ProtoMember(3)] | |||
| public int? DurationMs { get; set; } | |||
| [ProtoMember(4)] | |||
| public ExternalUrls? ExternalUrls { get; set; } | |||
| [ProtoMember(5)] | |||
| public Uri? Href { get; set; } | |||
| [ProtoMember(6)] | |||
| public string? Id { get; set; } | |||
| [ProtoMember(7)] | |||
| public string? Name { get; set; } | |||
| [ProtoMember(8)] | |||
| public int? Popularity { get; set; } | |||
| [ProtoMember(9)] | |||
| public int? TrackNumber { get; set; } | |||
| [ProtoMember(10)] | |||
| public string? Type { get; set; } | |||
| [ProtoMember(11)] | |||
| public string? Uri { get; set; } | |||
| } | |||
| [ProtoContract] | |||
| public partial class Album | |||
| { | |||
| [ProtoMember(1)] | |||
| public Uri? Href { get; set; } | |||
| [ProtoMember(2)] | |||
| public string? Id { get; set; } | |||
| [ProtoMember(3)] | |||
| public Image[]? Images { get; set; } | |||
| [ProtoMember(4)] | |||
| public string? Name { get; set; } | |||
| [ProtoMember(5)] | |||
| public DateTimeOffset? ReleaseDate { get; set; } | |||
| [ProtoMember(6)] | |||
| public int? TotalTracks { get; set; } | |||
| [ProtoMember(7)] | |||
| public string? Type { get; set; } | |||
| [ProtoMember(8)] | |||
| public string? Uri { get; set; } | |||
| } | |||
| [ProtoContract] | |||
| public partial class Image | |||
| { | |||
| [ProtoMember(1)] | |||
| public int? Height { get; set; } | |||
| [ProtoMember(2)] | |||
| public Uri? Url { get; set; } | |||
| [ProtoMember(3)] | |||
| public int? Width { get; set; } | |||
| } | |||
| [ProtoContract] | |||
| public partial class Artist | |||
| { | |||
| [ProtoMember(1)] | |||
| public ExternalUrls? ExternalUrls { get; set; } | |||
| [ProtoMember(2)] | |||
| public Uri? Href { get; set; } | |||
| [ProtoMember(3)] | |||
| public string? Id { get; set; } | |||
| [ProtoMember(4)] | |||
| public string? Name { get; set; } | |||
| [ProtoMember(5)] | |||
| public string? Type { get; set; } | |||
| [ProtoMember(6)] | |||
| public string? Uri { get; set; } | |||
| } | |||
| [ProtoContract] | |||
| public partial class ExternalUrls | |||
| { | |||
| [ProtoMember(1)] | |||
| public Uri? Spotify { get; set; } | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,21 @@ | |||
| using ProtoBuf; | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace GrpcShared.DTO.Search | |||
| { | |||
| [ProtoContract] | |||
| public class SearchRequest : TokenMessage | |||
| { | |||
| [ProtoMember(1)] | |||
| public string? Query { get; set; } | |||
| [ProtoMember(2)] | |||
| public string? Type { get; set; } | |||
| } | |||
| } | |||
| @@ -0,0 +1,127 @@ | |||
| using ProtoBuf; | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace GrpcShared.DTO.Search | |||
| { | |||
| [ProtoContract] | |||
| public class SearchResponse : StatusCodeMessage | |||
| { | |||
| [ProtoMember(1)] | |||
| public Tracks? Tracks { get; set; } | |||
| } | |||
| [ProtoContract] | |||
| public class Tracks | |||
| { | |||
| [ProtoMember(1)] | |||
| public Uri? Href { get; set; } | |||
| [ProtoMember(2)] | |||
| public List<Item>? Items { get; set; } | |||
| } | |||
| [ProtoContract] | |||
| public class Item | |||
| { | |||
| [ProtoMember(1)] | |||
| public Album? Album { get; set; } | |||
| [ProtoMember(2)] | |||
| public Artist[]? Artists { get; set; } | |||
| [ProtoMember(3)] | |||
| public int? DurationMs { get; set; } | |||
| [ProtoMember(4)] | |||
| public ExternalUrls? ExternalUrls { get; set; } | |||
| [ProtoMember(5)] | |||
| public Uri? Href { get; set; } | |||
| [ProtoMember(6)] | |||
| public string? Id { get; set; } | |||
| [ProtoMember(7)] | |||
| public string? Name { get; set; } | |||
| [ProtoMember(8)] | |||
| public int? Popularity { get; set; } | |||
| [ProtoMember(9)] | |||
| public int? TrackNumber { get; set; } | |||
| [ProtoMember(10)] | |||
| public string? Type { get; set; } | |||
| [ProtoMember(11)] | |||
| public string? Uri { get; set; } | |||
| } | |||
| [ProtoContract] | |||
| public class Album | |||
| { | |||
| [ProtoMember(1)] | |||
| public Uri? Href { get; set; } | |||
| [ProtoMember(2)] | |||
| public string? Id { get; set; } | |||
| [ProtoMember(3)] | |||
| public Image[]? Images { get; set; } | |||
| [ProtoMember(4)] | |||
| public string? Name { get; set; } | |||
| [ProtoMember(5)] | |||
| public string? ReleaseDate { get; set; } | |||
| [ProtoMember(6)] | |||
| public int? TotalTracks { get; set; } | |||
| [ProtoMember(7)] | |||
| public string? Type { get; set; } | |||
| [ProtoMember(8)] | |||
| public string? Uri { get; set; } | |||
| } | |||
| [ProtoContract] | |||
| public class Image | |||
| { | |||
| [ProtoMember(1)] | |||
| public int? Height { get; set; } | |||
| [ProtoMember(2)] | |||
| public Uri? Url { get; set; } | |||
| [ProtoMember(3)] | |||
| public int? Width { get; set; } | |||
| } | |||
| [ProtoContract] | |||
| public class Artist | |||
| { | |||
| [ProtoMember(1)] | |||
| public ExternalUrls? ExternalUrls { get; set; } | |||
| [ProtoMember(2)] | |||
| public Uri? Href { get; set; } | |||
| [ProtoMember(3)] | |||
| public string? Id { get; set; } | |||
| [ProtoMember(4)] | |||
| public string? Name { get; set; } | |||
| [ProtoMember(5)] | |||
| public string? Type { get; set; } | |||
| [ProtoMember(6)] | |||
| public string? Uri { get; set; } | |||
| } | |||
| [ProtoContract] | |||
| public class ExternalUrls | |||
| { | |||
| [ProtoMember(1)] | |||
| public Uri? Spotify { get; set; } | |||
| } | |||
| } | |||
| @@ -0,0 +1,23 @@ | |||
| using ProtoBuf; | |||
| using System.Net; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace GrpcShared.DTO | |||
| { | |||
| [ProtoContract] | |||
| [ProtoInclude(5, typeof(Search.SearchResponse))] | |||
| [ProtoInclude(6, typeof(TopItem.TopItemResponse))] | |||
| [ProtoInclude(7, typeof(Track.MultipleTrack.MultipleTrackResponse))] | |||
| [ProtoInclude(8, typeof(Track.SingleTrack.SingleTrackResponse))] | |||
| [ProtoInclude(9, typeof(TrackByID.TrackResponse))] | |||
| [ProtoInclude(10, typeof(User.UserInfoResponse))] | |||
| [ProtoInclude(11, typeof(Track.CurrentTrackResponse))] | |||
| [ProtoInclude(12, typeof(Auth.RefreshTokenResponse))] | |||
| public class StatusCodeMessage | |||
| { | |||
| [ProtoMember(1)] | |||
| public HttpStatusCode ResponseMsg { get; set; } | |||
| } | |||
| } | |||
| @@ -0,0 +1,31 @@ | |||
| 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 | |||
| { | |||
| [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)] | |||
| public class TokenMessage | |||
| { | |||
| [ProtoMember(1)] | |||
| public string? Token { get; set; } | |||
| [ProtoMember(2)] | |||
| public string? RefreshToken { get; set; } | |||
| } | |||
| } | |||
| @@ -0,0 +1,24 @@ | |||
| using ProtoBuf; | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.ComponentModel; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace GrpcShared.DTO.TopItem | |||
| { | |||
| [ProtoContract] | |||
| public class TopItemRequest : TokenMessage | |||
| { | |||
| [ProtoMember(1)] | |||
| [DefaultValue(true)] | |||
| public bool IsTracks { get; set; } = true; | |||
| [ProtoMember(2)] | |||
| public int? Limit { get; set; } | |||
| [ProtoMember(3)] | |||
| public int? Offset { get; set; } | |||
| } | |||
| } | |||
| @@ -0,0 +1,63 @@ | |||
| using Newtonsoft.Json; | |||
| using ProtoBuf; | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace GrpcShared.DTO.TopItem | |||
| { | |||
| [ProtoContract] | |||
| public class TopItemResponse : StatusCodeMessage | |||
| { | |||
| [ProtoMember(1)] | |||
| [JsonProperty("items")] | |||
| public Item[]? Items { get; set; } | |||
| [ProtoMember(2)] | |||
| [JsonProperty("total")] | |||
| public int? Total { get; set; } | |||
| [ProtoMember(3)] | |||
| [JsonProperty("limit")] | |||
| public int? Limit { get; set; } | |||
| [ProtoMember(4)] | |||
| [JsonProperty("offset")] | |||
| public int? Offset { get; set; } | |||
| [ProtoMember(5)] | |||
| [JsonProperty("href")] | |||
| public Uri? Href { get; set; } | |||
| } | |||
| [ProtoContract] | |||
| public partial class Item | |||
| { | |||
| [ProtoMember(1)] | |||
| [JsonProperty("genres")] | |||
| public string[]? Genres { get; set; } | |||
| [ProtoMember(2)] | |||
| [JsonProperty("id")] | |||
| public string? Id { get; set; } | |||
| [ProtoMember(3)] | |||
| [JsonProperty("images")] | |||
| public Image[]? Images { get; set; } | |||
| [ProtoMember(4)] | |||
| [JsonProperty("name")] | |||
| public string? Name { get; set; } | |||
| [ProtoMember(5)] | |||
| [JsonProperty("uri")] | |||
| public string? Uri { get; set; } | |||
| } | |||
| [ProtoContract] | |||
| public partial class Image | |||
| { | |||
| [ProtoMember(1)] | |||
| [JsonProperty("height")] | |||
| public int? Height { get; set; } | |||
| [ProtoMember(2)] | |||
| [JsonProperty("url")] | |||
| public Uri? Url { get; set; } | |||
| [ProtoMember(3)] | |||
| [JsonProperty("width")] | |||
| public int? Width { get; set; } | |||
| } | |||
| } | |||
| @@ -0,0 +1,90 @@ | |||
| using Newtonsoft.Json; | |||
| using ProtoBuf; | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace GrpcShared.DTO.Track | |||
| { | |||
| [ProtoContract] | |||
| public class CurrentTrackResponse : StatusCodeMessage | |||
| { | |||
| [ProtoMember(1)] | |||
| [JsonProperty("timestamp")] | |||
| public string? Timestamp{ get; set; } | |||
| [ProtoMember(2)] | |||
| [JsonProperty("progress_ms")] | |||
| public int? ProgressMs { get; set; } | |||
| [ProtoMember(3)] | |||
| [JsonProperty("is_playing")] | |||
| public bool? IsPlaying { get; set; } | |||
| [ProtoMember(4)] | |||
| [JsonProperty("item")] | |||
| public Item? Item { get; set; } | |||
| } | |||
| [ProtoContract] | |||
| public class Item | |||
| { | |||
| [ProtoMember(1)] | |||
| [JsonProperty("album")] | |||
| public Album? Album { get; set; } | |||
| [ProtoMember(2)] | |||
| [JsonProperty("artists")] | |||
| public Artist[]? Artists { get; set; } | |||
| [ProtoMember(3)] | |||
| [JsonProperty("id")] | |||
| public string? Id { get; set; } | |||
| [ProtoMember(4)] | |||
| [JsonProperty("name")] | |||
| public string? Name { get; set; } | |||
| [ProtoMember(5)] | |||
| [JsonProperty("href")] | |||
| public string? Href { get; set; } | |||
| } | |||
| [ProtoContract] | |||
| public class Album | |||
| { | |||
| [ProtoMember(1)] | |||
| [JsonProperty("id")] | |||
| public string? Id { get; set; } | |||
| [ProtoMember(2)] | |||
| [JsonProperty("name")] | |||
| public string? Name { get; set; } | |||
| [ProtoMember(3)] | |||
| [JsonProperty("images")] | |||
| public Image[]? Images { get; set; } | |||
| [ProtoMember(4)] | |||
| [JsonProperty("href")] | |||
| public string? Href { get; set; } | |||
| } | |||
| [ProtoContract] | |||
| public class Artist | |||
| { | |||
| [ProtoMember(1)] | |||
| [JsonProperty("id")] | |||
| public string? Id { get; set; } | |||
| [ProtoMember(2)] | |||
| [JsonProperty("name")] | |||
| public string? Name { get; set; } | |||
| [ProtoMember(3)] | |||
| [JsonProperty("href")] | |||
| public string? Href { get; set; } | |||
| } | |||
| [ProtoContract] | |||
| public class Image | |||
| { | |||
| [ProtoMember(1)] | |||
| [JsonProperty("height")] | |||
| public int? Height{ get; set; } | |||
| [ProtoMember(2)] | |||
| [JsonProperty("url")] | |||
| public string? Url { get; set; } | |||
| [ProtoMember(3)] | |||
| [JsonProperty("width")] | |||
| public int? Width { get; set; } | |||
| } | |||
| } | |||
| @@ -0,0 +1,17 @@ | |||
| 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:TokenMessage | |||
| { | |||
| [ProtoMember(1)] | |||
| public List<string>? Ids { 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 : StatusCodeMessage | |||
| { | |||
| [ProtoMember(1)] | |||
| public List<AudioFeature>? Audio_Features { get; set; } | |||
| } | |||
| [ProtoContract] | |||
| public class AudioFeature | |||
| { | |||
| [ProtoMember(1)] | |||
| public float? Danceability { get; set; } | |||
| [ProtoMember(2)] | |||
| public float? Energy { get; set; } | |||
| [ProtoMember(3)] | |||
| public int? Key { get; set; } | |||
| [ProtoMember(4)] | |||
| public float? Loudness { get; set; } | |||
| [ProtoMember(5)] | |||
| public int? Mode { get; set; } | |||
| [ProtoMember(6)] | |||
| public float? Speechiness { get; set; } | |||
| [ProtoMember(7)] | |||
| public float? Acousticness { get; set; } | |||
| [ProtoMember(8)] | |||
| public int? Instrumentalness { get; set; } | |||
| [ProtoMember(9)] | |||
| public float? Liveness { get; set; } | |||
| [ProtoMember(10)] | |||
| public float? Valence { get; set; } | |||
| [ProtoMember(11)] | |||
| public float? 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 int? DurationMs { get; set; } | |||
| [ProtoMember(18)] | |||
| public int? TimeSignature { get; set; } | |||
| } | |||
| } | |||
| @@ -0,0 +1,17 @@ | |||
| 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 : TokenMessage | |||
| { | |||
| [ProtoMember(1)] | |||
| public List<string>? Ids { get; set; } | |||
| } | |||
| } | |||
| @@ -0,0 +1,17 @@ | |||
| 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 : TokenMessage | |||
| { | |||
| [ProtoMember(1)] | |||
| public string? Id { get; set; } | |||
| } | |||
| } | |||
| @@ -0,0 +1,62 @@ | |||
| using ProtoBuf; | |||
| namespace GrpcShared.DTO.Track.SingleTrack | |||
| { | |||
| [ProtoContract] | |||
| public class SingleTrackResponse : StatusCodeMessage | |||
| { | |||
| [ProtoMember(1)] | |||
| public float? Danceability { get; set; } | |||
| [ProtoMember(2)] | |||
| public float? Energy { get; set; } | |||
| [ProtoMember(3)] | |||
| public int? Key { get; set; } | |||
| [ProtoMember(4)] | |||
| public float? Loudness { get; set; } | |||
| [ProtoMember(5)] | |||
| public int? Mode { get; set; } | |||
| [ProtoMember(6)] | |||
| public float? Speechiness { get; set; } | |||
| [ProtoMember(7)] | |||
| public float? Acousticness { get; set; } | |||
| [ProtoMember(8)] | |||
| public int? Instrumentalness { get; set; } | |||
| [ProtoMember(9)] | |||
| public float? Liveness { get; set; } | |||
| [ProtoMember(10)] | |||
| public float? Valence { get; set; } | |||
| [ProtoMember(11)] | |||
| public float? 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 int? DurationMs { get; set; } | |||
| [ProtoMember(18)] | |||
| public int? TimeSignature { get; set; } | |||
| } | |||
| } | |||
| @@ -0,0 +1,19 @@ | |||
| using Newtonsoft.Json; | |||
| using ProtoBuf; | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace GrpcShared.DTO.TrackByID | |||
| { | |||
| [ProtoContract] | |||
| public class TrackRequest:TokenMessage | |||
| { | |||
| [ProtoMember(1)] | |||
| [JsonProperty("id")] | |||
| public string? TrackID { get; set; } | |||
| } | |||
| } | |||
| @@ -0,0 +1,39 @@ | |||
| using GrpcShared.DTO.Track; | |||
| using Newtonsoft.Json; | |||
| using ProtoBuf; | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace GrpcShared.DTO.TrackByID | |||
| { | |||
| [ProtoContract] | |||
| public class TrackResponse : StatusCodeMessage | |||
| { | |||
| [ProtoMember(1)] | |||
| [JsonProperty("album")] | |||
| public Album? Album { get; set; } | |||
| [ProtoMember(2)] | |||
| [JsonProperty("artists")] | |||
| public Artist[]? Artists { get; set; } | |||
| [ProtoMember(3)] | |||
| [JsonProperty("duration_ms")] | |||
| public long? DurationMs { get; set; } | |||
| [ProtoMember(4)] | |||
| [JsonProperty("href")] | |||
| public Uri? Href { get; set; } | |||
| [ProtoMember(5)] | |||
| [JsonProperty("id")] | |||
| public string? Id { get; set; } | |||
| [ProtoMember(6)] | |||
| [JsonProperty("name")] | |||
| public string? Name { get; set; } | |||
| } | |||
| } | |||
| @@ -0,0 +1,19 @@ | |||
| using Newtonsoft.Json; | |||
| using ProtoBuf; | |||
| namespace GrpcShared.DTO.User | |||
| { | |||
| [ProtoContract] | |||
| public class UserInfoResponse : StatusCodeMessage | |||
| { | |||
| [ProtoMember(1)] | |||
| [JsonProperty("email")] | |||
| public string? Email { get; set; } | |||
| [ProtoMember(2)] | |||
| [JsonProperty("id")] | |||
| public string? Id { get; set; } | |||
| [ProtoMember(3)] | |||
| [JsonProperty("display_name")] | |||
| public string? DisplayName{ 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"; | |||
| } | |||
| } | |||
| @@ -0,0 +1,21 @@ | |||
| <Project Sdk="Microsoft.NET.Sdk"> | |||
| <PropertyGroup> | |||
| <OutputType>Library</OutputType> | |||
| <TargetFramework>net6.0</TargetFramework> | |||
| <ImplicitUsings>enable</ImplicitUsings> | |||
| <Nullable>enable</Nullable> | |||
| </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> | |||
| </PackageReference> | |||
| <PackageReference Include="protobuf-net.Grpc" Version="1.0.171" /> | |||
| </ItemGroup> | |||
| </Project> | |||
| @@ -0,0 +1,17 @@ | |||
| using GrpcShared.DTO; | |||
| using GrpcShared.DTO.Auth; | |||
| using GrpcShared.DTO.User; | |||
| using ProtoBuf.Grpc.Configuration; | |||
| namespace GrpcShared.Interfaces | |||
| { | |||
| [Service] | |||
| public interface IAuthService | |||
| { | |||
| Task<TokenResponse> GetAccessToken(TokenRequest code); | |||
| Task<CodeRequest> GetAuthParams(); | |||
| Task<UserInfoResponse> GetUserInfo(TokenMessage token); | |||
| Task<RefreshTokenResponse> RefreshAccessToken(TokenMessage msg); | |||
| //Task<ClientSecrets> GetClientSecrets(); | |||
| } | |||
| } | |||
| @@ -0,0 +1,19 @@ | |||
| using GrpcShared.DTO; | |||
| using GrpcShared.DTO.TopItem; | |||
| using GrpcShared.DTO.Track; | |||
| using ProtoBuf.Grpc.Configuration; | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace GrpcShared.Interfaces | |||
| { | |||
| [Service] | |||
| public interface IStatsService | |||
| { | |||
| Task<CurrentTrackResponse> GetCurrentlyPlayingTrack(TokenMessage token); | |||
| Task<TopItemResponse> GetTopItems(TopItemRequest request); | |||
| } | |||
| } | |||
| @@ -0,0 +1,24 @@ | |||
| using GrpcShared.DTO.Search; | |||
| using GrpcShared.DTO.Track.MultipleTrack; | |||
| using GrpcShared.DTO.Track.SaveTracks; | |||
| using GrpcShared.DTO.Track.SingleTrack; | |||
| using GrpcShared.DTO.TrackByID; | |||
| using ProtoBuf.Grpc.Configuration; | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace GrpcShared.Interfaces | |||
| { | |||
| [Service] | |||
| public interface ITrackService | |||
| { | |||
| Task<SearchResponse> ListSearchAsync(SearchRequest request); | |||
| Task<SingleTrackResponse> ListSingleTrackAsync(SingleTrackRequest request); | |||
| Task<MultipleTrackResponse> ListMultipleTrackAsync(MultipleTrackRequest request); | |||
| Task SaveTracks(SaveTracksRequest request); | |||
| Task<TrackResponse> GetById(TrackRequest request); | |||
| } | |||
| } | |||
| @@ -0,0 +1,31 @@ | |||
| <Project Sdk="Microsoft.NET.Sdk.Web"> | |||
| <PropertyGroup> | |||
| <TargetFramework>net6.0</TargetFramework> | |||
| <Nullable>enable</Nullable> | |||
| <ImplicitUsings>enable</ImplicitUsings> | |||
| </PropertyGroup> | |||
| <ItemGroup> | |||
| <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="Newtonsoft.Json" Version="13.0.1" /> | |||
| <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" /> | |||
| <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> | |||
| <ItemGroup> | |||
| <Folder Include="Services\" /> | |||
| </ItemGroup> | |||
| </Project> | |||
| @@ -0,0 +1,80 @@ | |||
| using GrpcShared; | |||
| using SpotifyService.Services; | |||
| using Microsoft.AspNetCore.Server.Kestrel.Core; | |||
| using ProtoBuf.Grpc.Server; | |||
| using Microsoft.Extensions.Options; | |||
| using GrpcShared.DTO.Auth; | |||
| using Blazored.LocalStorage; | |||
| var builder = WebApplication.CreateBuilder(args); | |||
| #if DEBUG | |||
| builder.WebHost.ConfigureKestrel(options => | |||
| { | |||
| options.ListenLocalhost(5050, o => o.Protocols = | |||
| HttpProtocols.Http2); | |||
| options.ListenLocalhost(5051, o => o.Protocols = | |||
| HttpProtocols.Http1AndHttp2); | |||
| }); | |||
| #endif | |||
| builder.Services.AddHttpClient("HttpClient", c => | |||
| { | |||
| c.BaseAddress = new Uri(SpotifyService.GLOBALS.SPOTIFYURL); | |||
| c.DefaultRequestHeaders.Add("Accept", SpotifyService.GLOBALS.MEDIATYPE); | |||
| }); | |||
| builder.Services.AddOptions(); | |||
| // 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 | |||
| builder.Services.AddControllersWithViews(); | |||
| builder.Services.AddRazorPages(); | |||
| builder.Services.AddEndpointsApiExplorer(); | |||
| builder.Services.AddGrpc(); | |||
| builder.Services.AddCodeFirstGrpc(); | |||
| builder.Services.AddCodeFirstGrpcReflection(); | |||
| builder.Services.AddBlazoredLocalStorage(); | |||
| //call spotify api | |||
| builder.Services.AddHttpClient(); | |||
| builder.Services.Configure<CodeRequest>(builder.Configuration.GetSection("AuthParams")); | |||
| var app = builder.Build(); | |||
| // Configure the HTTP request pipeline. | |||
| if (app.Environment.IsDevelopment()) | |||
| { | |||
| app.UseWebAssemblyDebugging(); | |||
| } | |||
| else | |||
| { | |||
| app.UseExceptionHandler("/Error"); | |||
| // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. | |||
| app.UseHsts(); | |||
| } | |||
| app.UseHttpsRedirection(); | |||
| //run blazor project by running grpc server | |||
| app.UseBlazorFrameworkFiles(); | |||
| app.UseStaticFiles(); | |||
| app.UseRouting(); | |||
| //for http2 -> http conversion | |||
| app.UseGrpcWeb(); | |||
| app.MapRazorPages(); | |||
| app.MapControllers(); | |||
| //app.MapGrpcService<WeatherService>(); | |||
| app.MapGrpcService<AuthService>().EnableGrpcWeb(); | |||
| app.MapGrpcService<TrackService>().EnableGrpcWeb(); | |||
| app.MapGrpcService<StatsService>().EnableGrpcWeb(); | |||
| app.MapCodeFirstGrpcReflectionService(); | |||
| app.MapFallbackToFile("index.html"); | |||
| app.Run(); | |||
| @@ -0,0 +1,30 @@ | |||
| { | |||
| "iisSettings": { | |||
| "windowsAuthentication": false, | |||
| "anonymousAuthentication": true, | |||
| "iisExpress": { | |||
| "applicationUrl": "http://localhost:28725", | |||
| "sslPort": 44342 | |||
| } | |||
| }, | |||
| "profiles": { | |||
| "IIS Express": { | |||
| "commandName": "IISExpress", | |||
| "launchBrowser": true, | |||
| "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", | |||
| "environmentVariables": { | |||
| "ASPNETCORE_ENVIRONMENT": "Development" | |||
| } | |||
| }, | |||
| "IdentityProvider": { | |||
| "commandName": "Project", | |||
| "dotnetRunMessages": "true", | |||
| "launchBrowser": true, | |||
| "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", | |||
| "applicationUrl": "https://localhost:5001;http://localhost:5000", | |||
| "environmentVariables": { | |||
| "ASPNETCORE_ENVIRONMENT": "Development" | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,8 @@ | |||
| { | |||
| "Logging": { | |||
| "LogLevel": { | |||
| "Default": "Information", | |||
| "Microsoft.AspNetCore": "Warning" | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,20 @@ | |||
| { | |||
| "Logging": { | |||
| "LogLevel": { | |||
| "Default": "Information", | |||
| "Microsoft.AspNetCore": "Warning" | |||
| } | |||
| }, | |||
| "AllowedHosts": "*", | |||
| "Kestrel": { | |||
| "EndpointDefaults": { | |||
| "Protocols": "Http2" | |||
| } | |||
| }, | |||
| "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" | |||
| } | |||
| } | |||
| @@ -3,13 +3,15 @@ Microsoft Visual Studio Solution File, Format Version 12.00 | |||
| # Visual Studio Version 17 | |||
| VisualStudioVersion = 17.2.32630.192 | |||
| MinimumVisualStudioVersion = 10.0.40219.1 | |||
| Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NemAnCore", "NemAnCore\NemAnCore.csproj", "{74DCDAC5-76D7-4E06-A3F2-DE22CD37FB89}" | |||
| Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NemAnBlazor", "NemAnCore\NemAnBlazor.csproj", "{74DCDAC5-76D7-4E06-A3F2-DE22CD37FB89}" | |||
| EndProject | |||
| Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "gRPCServer", "gRPCServer\gRPCServer.csproj", "{9E8FA4BC-BF52-47E2-8E61-A4151505ED7C}" | |||
| Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SpotifyService", "gRPCServer\SpotifyService.csproj", "{9E8FA4BC-BF52-47E2-8E61-A4151505ED7C}" | |||
| EndProject | |||
| Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AuthService", "AuthService\AuthService.csproj", "{CB9B1DAB-3F49-4156-8812-203536FE5E54}" | |||
| Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SpotifyWorker", "SpotifyWorker\SpotifyWorker.csproj", "{0CE36C12-E8D7-424A-9161-0A05306CD8BC}" | |||
| EndProject | |||
| Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SpotifyWorker", "SpotifyWorker\SpotifyWorker.csproj", "{0CE36C12-E8D7-424A-9161-0A05306CD8BC}" | |||
| Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IdentityProvider", "IdentityProvider\IdentityProvider.csproj", "{D160945A-5068-4D6A-A09D-5DD7A9EFBC01}" | |||
| EndProject | |||
| Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GrpcShared", "GrpcShared\GrpcShared.csproj", "{C142D6DF-066A-4ADA-86A3-1C736136C1FA}" | |||
| EndProject | |||
| Global | |||
| GlobalSection(SolutionConfigurationPlatforms) = preSolution | |||
| @@ -25,14 +27,18 @@ Global | |||
| {9E8FA4BC-BF52-47E2-8E61-A4151505ED7C}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||
| {9E8FA4BC-BF52-47E2-8E61-A4151505ED7C}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||
| {9E8FA4BC-BF52-47E2-8E61-A4151505ED7C}.Release|Any CPU.Build.0 = Release|Any CPU | |||
| {CB9B1DAB-3F49-4156-8812-203536FE5E54}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||
| {CB9B1DAB-3F49-4156-8812-203536FE5E54}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||
| {CB9B1DAB-3F49-4156-8812-203536FE5E54}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||
| {CB9B1DAB-3F49-4156-8812-203536FE5E54}.Release|Any CPU.Build.0 = Release|Any CPU | |||
| {0CE36C12-E8D7-424A-9161-0A05306CD8BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||
| {0CE36C12-E8D7-424A-9161-0A05306CD8BC}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||
| {0CE36C12-E8D7-424A-9161-0A05306CD8BC}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||
| {0CE36C12-E8D7-424A-9161-0A05306CD8BC}.Release|Any CPU.Build.0 = Release|Any CPU | |||
| {D160945A-5068-4D6A-A09D-5DD7A9EFBC01}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||
| {D160945A-5068-4D6A-A09D-5DD7A9EFBC01}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||
| {D160945A-5068-4D6A-A09D-5DD7A9EFBC01}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||
| {D160945A-5068-4D6A-A09D-5DD7A9EFBC01}.Release|Any CPU.Build.0 = Release|Any CPU | |||
| {C142D6DF-066A-4ADA-86A3-1C736136C1FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||
| {C142D6DF-066A-4ADA-86A3-1C736136C1FA}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||
| {C142D6DF-066A-4ADA-86A3-1C736136C1FA}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||
| {C142D6DF-066A-4ADA-86A3-1C736136C1FA}.Release|Any CPU.Build.0 = Release|Any CPU | |||
| EndGlobalSection | |||
| GlobalSection(SolutionProperties) = preSolution | |||
| HideSolutionNode = FALSE | |||
| @@ -1,6 +1,11 @@ | |||
| <Router AppAssembly="@typeof(App).Assembly"> | |||
| @using NemAnBlazor.Pages | |||
| <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 authorizing the user.</text> | |||
| </Authorizing> | |||
| </AuthorizeRouteView> | |||
| <FocusOnNavigate RouteData="@routeData" Selector="h1" /> | |||
| </Found> | |||
| <NotFound> | |||
| @@ -0,0 +1,31 @@ | |||
| <Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly"> | |||
| <PropertyGroup> | |||
| <TargetFramework>net6.0</TargetFramework> | |||
| <Nullable>enable</Nullable> | |||
| <ImplicitUsings>enable</ImplicitUsings> | |||
| </PropertyGroup> | |||
| <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> | |||
| <Optimize>True</Optimize> | |||
| </PropertyGroup> | |||
| <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'"> | |||
| <Optimize>False</Optimize> | |||
| </PropertyGroup> | |||
| <ItemGroup> | |||
| <PackageReference Include="Blazored.LocalStorage" Version="4.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" /> | |||
| </ItemGroup> | |||
| <ItemGroup> | |||
| <ProjectReference Include="..\GrpcShared\GrpcShared.csproj" /> | |||
| </ItemGroup> | |||
| </Project> | |||
| @@ -1,14 +0,0 @@ | |||
| <Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly"> | |||
| <PropertyGroup> | |||
| <TargetFramework>net6.0</TargetFramework> | |||
| <Nullable>enable</Nullable> | |||
| <ImplicitUsings>enable</ImplicitUsings> | |||
| </PropertyGroup> | |||
| <ItemGroup> | |||
| <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="6.0.7" /> | |||
| <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="6.0.7" PrivateAssets="all" /> | |||
| </ItemGroup> | |||
| </Project> | |||
| @@ -0,0 +1,33 @@ | |||
| @page "/callback" | |||
| @using NemAnBlazor.Services.Interfaces | |||
| @inject NavigationManager NavigationMgr | |||
| @inject IAuthClientService AuthService | |||
| @inject Blazored.LocalStorage.ILocalStorageService localStorage | |||
| <PageTitle>Redirecting...</PageTitle> | |||
| <p role="status">Loading...</p> | |||
| @code { | |||
| protected override async Task OnInitializedAsync() | |||
| { | |||
| string url = NavigationMgr.Uri; | |||
| //code is the only parameter in the url | |||
| string code = url.Split("=")[1]; | |||
| var response = await AuthService.GetAccessToken(new GrpcShared.DTO.Auth.TokenRequest { Code = code}); | |||
| //if (response.access_token == null) NavigationMgr.NavigateTo("/"); | |||
| //store access token in local storage | |||
| await localStorage.SetItemAsync("token", response.AccessToken); | |||
| await localStorage.SetItemAsync("refresh_token", response.RefreshToken); | |||
| //redirect to home | |||
| NavigationMgr.NavigateTo("/home"); | |||
| } | |||
| } | |||
| @@ -0,0 +1,67 @@ | |||
| @page "/search" | |||
| @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.LocalStorage.ILocalStorageService localStorage | |||
| @inject ITrackClientService SearchService | |||
| @inject IAuthClientService AuthService | |||
| <PageTitle>Search</PageTitle> | |||
| <h1>Search</h1> | |||
| <button class="btn btn-primary" @onclick="Click">Click me</button> | |||
| @code { | |||
| //protected override async Task OnInitializedAsync() | |||
| //{ | |||
| //MultipleTrackRequest mreq = new() { Ids = new List<string>(){"3JAeYOjyJodI4PRs44lx2l", "6clZa1yrZe7pJrYFUcD9KW"}, Token = token }; | |||
| //MultipleTrackResponse multipleTrackResponse = await SearchService.GetListMultipleTrackAsync(mreq); | |||
| //SingleTrackRequest singleTrackRequest = new() { Id = "3JAeYOjyJodI4PRs44lx2l", Token = token }; | |||
| //SingleTrackResponse singleTrackResponse = await SearchService.GetListSingleTrackAsync(singleTrackRequest); | |||
| //} | |||
| private async Task Click() | |||
| { | |||
| var token = await localStorage.GetItemAsync<string>("token"); | |||
| string refreshT = await localStorage.GetItemAsync<string>("refresh_token"); | |||
| TokenMessage tokenM = new TokenMessage { Token = token, RefreshToken = refreshT }; | |||
| SearchRequest request = new() { Query = "aitch", Type = "track", Token = token }; | |||
| try | |||
| { | |||
| SearchResponse searchResponse = await SearchService.GetListSearchAsync(request); | |||
| if (searchResponse.ResponseMsg == System.Net.HttpStatusCode.Unauthorized) | |||
| { | |||
| string? tempToken = await SpotifyHelper.TryRefreshToken(AuthService, tokenM, localStorage); | |||
| token = tempToken == null ? token : tempToken; | |||
| } | |||
| } | |||
| catch (RpcException e) | |||
| { | |||
| if (e.StatusCode == StatusCode.Cancelled) | |||
| { | |||
| return; | |||
| } | |||
| throw; | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,72 @@ | |||
| @page "/home" | |||
| @using Grpc.Core | |||
| @using GrpcShared.DTO | |||
| @using GrpcShared.DTO.Track | |||
| @using NemAnBlazor.Services.Interfaces | |||
| @using System.Net | |||
| @inject Blazored.LocalStorage.ILocalStorageService localStorage | |||
| @inject IStatsClientService spotifyService | |||
| @inject ITrackClientService trackService | |||
| @inject IAuthClientService AuthService | |||
| <h3>Home</h3> | |||
| <p>login radi</p> | |||
| @code { | |||
| protected override async Task OnInitializedAsync() | |||
| { | |||
| string tokenS = await localStorage.GetItemAsync<string>("token"); | |||
| string refreshT = await localStorage.GetItemAsync<string>("refresh_token"); | |||
| //tokenS = "BQBMgFm6jnFNWWeZEMGIRP_f-ENPid7Kw8JubAyuWAe4JK0S1DPFGlaAdZ_Fey6ePkCnz8-cqC0oyRmrciWUy5ISUTQKDe8PTQn4iBRMYCgM0n4GnS1xAErHJcm4Vpu2TAngk-4vQUOfTQRcedNTfCaHKP4uFJgTlTI7JHGrtB-_EZLnFcZ2OQe31oFQIJ1wM3ZtvwnN"; | |||
| TokenMessage token = new TokenMessage { Token = tokenS, RefreshToken = refreshT }; | |||
| try | |||
| { | |||
| CurrentTrackResponse track = await spotifyService.GetCurrentlyPlayingTrack(token); | |||
| //if token expired, refresh it | |||
| if (track.ResponseMsg == System.Net.HttpStatusCode.Unauthorized) | |||
| { | |||
| string? tempToken = await SpotifyHelper.TryRefreshToken(AuthService, token, localStorage); | |||
| 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; | |||
| } | |||
| //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 }); | |||
| 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, localStorage); | |||
| tokenS = tempToken == null ? tokenS : tempToken; | |||
| } | |||
| } | |||
| catch (RpcException e) | |||
| { | |||
| if (e.StatusCode == StatusCode.Cancelled) | |||
| { | |||
| return; | |||
| } | |||
| throw; | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,49 @@ | |||
| @page "/" | |||
| @using Grpc.Net.Client | |||
| @using Grpc.Net.Client.Web | |||
| @using GrpcShared | |||
| @using GrpcShared.DTO.Auth | |||
| @using GrpcShared.DTO.Search | |||
| @using NemAnBlazor.Services.Interfaces | |||
| @inject NavigationManager NavigationManager | |||
| @inject IAuthClientService AuthService | |||
| @inject ITrackClientService SearchService | |||
| @using System.Security.Claims | |||
| <AuthorizeView> | |||
| <Authorized> | |||
| <p>Dobrodosli @context.User.Claims.FirstOrDefault(x => x.Type == "name")?.Value.ToUpper()</p> | |||
| </Authorized> | |||
| <NotAuthorized> | |||
| Nisi autorizovan. | |||
| <button class="btn btn-primary" @onclick="LoginUser">Login</button> | |||
| </NotAuthorized> | |||
| </AuthorizeView> | |||
| <PageTitle>Index</PageTitle> | |||
| <h1>Pozdrav Diligent!</h1> | |||
| Dobrodošli u našu NemAn aplikaciju. | |||
| @code { | |||
| private string? message; | |||
| private async Task LoginUser() | |||
| { | |||
| //var response = await SearchService.GetListSearchAsync(new GrpcShared.DTO.Search.SearchRequest() { Query="venom", Type = "track"}); | |||
| 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); | |||
| } | |||
| } | |||
| @@ -0,0 +1,7 @@ | |||
| @inject NavigationManager UriHelper | |||
| @code { | |||
| protected override void OnInitialized() | |||
| { | |||
| UriHelper.NavigateTo("/"); | |||
| } | |||
| } | |||
| @@ -1,6 +1,13 @@ | |||
| global using Microsoft.AspNetCore.Components.Authorization; | |||
| using Blazored.LocalStorage; | |||
| using Grpc.Net.Client; | |||
| using Grpc.Net.Client.Web; | |||
| using Microsoft.AspNetCore.Components; | |||
| using Microsoft.AspNetCore.Components.Web; | |||
| using Microsoft.AspNetCore.Components.WebAssembly.Hosting; | |||
| using NemAnCore; | |||
| using NemAnBlazor; | |||
| using NemAnBlazor.Services; | |||
| using NemAnBlazor.Services.Interfaces; | |||
| var builder = WebAssemblyHostBuilder.CreateDefault(args); | |||
| builder.RootComponents.Add<App>("#app"); | |||
| @@ -8,5 +15,22 @@ builder.RootComponents.Add<HeadOutlet>("head::after"); | |||
| builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); | |||
| builder.Services.AddScoped(_ => | |||
| { | |||
| var grpcWebHandler = new GrpcWebHandler(GrpcWebMode.GrpcWeb, new HttpClientHandler()); | |||
| var channel = GrpcChannel.ForAddress(builder.HostEnvironment.BaseAddress, new GrpcChannelOptions { HttpHandler = grpcWebHandler }); | |||
| return channel; | |||
| }); | |||
| builder.Services.AddAuthorizationCore(); | |||
| //builder.Services.AddScoped<AuthenticationStateProvider, AuthClientService>(); | |||
| builder.Services.AddScoped<ITrackClientService, TrackClientService>(); | |||
| builder.Services.AddScoped<IAuthClientService, AuthClientService>(); | |||
| builder.Services.AddScoped<IStatsClientService, StatsClientService>(); | |||
| builder.Services.AddBlazoredLocalStorage(); | |||
| builder.Services.AddScoped<AuthenticationStateProvider, AuthClientService>(); | |||
| builder.Services.AddAuthorizationCore(); | |||
| await builder.Build().RunAsync(); | |||
| @@ -1,30 +1,16 @@ | |||
| { | |||
| "iisSettings": { | |||
| "windowsAuthentication": false, | |||
| "anonymousAuthentication": true, | |||
| "iisExpress": { | |||
| "applicationUrl": "http://localhost:50338", | |||
| "sslPort": 44375 | |||
| } | |||
| "anonymousAuthentication": true | |||
| }, | |||
| "profiles": { | |||
| "NemAnCore": { | |||
| "commandName": "Project", | |||
| "dotnetRunMessages": true, | |||
| "launchBrowser": true, | |||
| "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", | |||
| "applicationUrl": "https://localhost:7229;http://localhost:5229", | |||
| "environmentVariables": { | |||
| "ASPNETCORE_ENVIRONMENT": "Development" | |||
| } | |||
| }, | |||
| "IIS Express": { | |||
| "commandName": "IISExpress", | |||
| "launchBrowser": true, | |||
| "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", | |||
| "environmentVariables": { | |||
| "ASPNETCORE_ENVIRONMENT": "Development" | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,99 @@ | |||
| using Grpc.Net.Client; | |||
| using GrpcShared.DTO.Auth; | |||
| 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.LocalStorage; | |||
| namespace NemAnBlazor.Services | |||
| { | |||
| public class AuthClientService : AuthenticationStateProvider, IAuthClientService | |||
| { | |||
| private IAuthService _serviceClient; | |||
| private ILocalStorageService _localStorage; | |||
| public AuthClientService(GrpcChannel grpcChannel, ILocalStorageService sessionStorage) | |||
| { | |||
| _serviceClient = grpcChannel.CreateGrpcService<IAuthService>(); | |||
| _localStorage = sessionStorage; | |||
| } | |||
| public async Task<TokenResponse> GetAccessToken(TokenRequest request) | |||
| { | |||
| return await _serviceClient.GetAccessToken(request); | |||
| } | |||
| //public override async Task<AuthenticationState> GetAuthenticationStateAsync() | |||
| //{ | |||
| // string token = await _localStorage.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; | |||
| //} | |||
| public async Task<CodeRequest> GetAuthParams() | |||
| { | |||
| return await _serviceClient.GetAuthParams(); | |||
| } | |||
| public async Task<UserInfoResponse> GetUserInfo(TokenMessage token) | |||
| { | |||
| return await _serviceClient.GetUserInfo(token); | |||
| } | |||
| public override async Task<AuthenticationState> GetAuthenticationStateAsync() | |||
| { | |||
| //return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity())); | |||
| await Task.Delay(500); | |||
| string token = await _localStorage.GetItemAsync<string>("token"); | |||
| string refreshT = await _localStorage.GetItemAsync<string>("refresh_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, RefreshToken = refreshT }); | |||
| 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!)); | |||
| ClaimsIdentity identity = new(claims, "jwt"); | |||
| //ClaimsIdentity identity = new(); | |||
| ClaimsPrincipal user = new(identity); | |||
| AuthenticationState state = new(user); | |||
| // NotifyAuthenticationStateChanged(Task.FromResult(state)); | |||
| return state; | |||
| } | |||
| public async Task<RefreshTokenResponse> RefreshAccessToken(TokenMessage token) | |||
| { | |||
| return await _serviceClient.RefreshAccessToken(token); | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,15 @@ | |||
| using GrpcShared; | |||
| using GrpcShared.DTO; | |||
| using GrpcShared.DTO.Auth; | |||
| using GrpcShared.DTO.User; | |||
| namespace NemAnBlazor.Services.Interfaces | |||
| { | |||
| public interface IAuthClientService | |||
| { | |||
| Task<TokenResponse> GetAccessToken(TokenRequest tokenRequest); | |||
| Task<CodeRequest> GetAuthParams(); | |||
| Task<UserInfoResponse> GetUserInfo(TokenMessage token); | |||
| Task<RefreshTokenResponse> RefreshAccessToken(TokenMessage token); | |||
| } | |||
| } | |||
| @@ -0,0 +1,12 @@ | |||
| using GrpcShared.DTO; | |||
| using GrpcShared.DTO.TopItem; | |||
| using GrpcShared.DTO.Track; | |||
| namespace NemAnBlazor.Services.Interfaces | |||
| { | |||
| public interface IStatsClientService | |||
| { | |||
| Task<CurrentTrackResponse> GetCurrentlyPlayingTrack(TokenMessage token); | |||
| Task<TopItemResponse> GetTopItems(TopItemRequest request); | |||
| } | |||
| } | |||
| @@ -0,0 +1,17 @@ | |||
| using GrpcShared.DTO.Search; | |||
| using GrpcShared.DTO.Track.MultipleTrack; | |||
| using GrpcShared.DTO.Track.SaveTracks; | |||
| using GrpcShared.DTO.Track.SingleTrack; | |||
| using GrpcShared.DTO.TrackByID; | |||
| 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); | |||
| Task<TrackResponse> GetById(TrackRequest request); | |||
| } | |||
| } | |||
| @@ -0,0 +1,28 @@ | |||
| using Grpc.Net.Client; | |||
| using GrpcShared.DTO; | |||
| using GrpcShared.DTO.TopItem; | |||
| using GrpcShared.DTO.Track; | |||
| using GrpcShared.Interfaces; | |||
| using NemAnBlazor.Services.Interfaces; | |||
| using ProtoBuf.Grpc.Client; | |||
| namespace NemAnBlazor.Services | |||
| { | |||
| public class StatsClientService : IStatsClientService | |||
| { | |||
| private IStatsService _serviceClient; | |||
| public StatsClientService(GrpcChannel channel) | |||
| { | |||
| _serviceClient = channel.CreateGrpcService<IStatsService>(); | |||
| } | |||
| public async Task<CurrentTrackResponse> GetCurrentlyPlayingTrack(TokenMessage token) | |||
| { | |||
| return await _serviceClient.GetCurrentlyPlayingTrack(token); | |||
| } | |||
| public async Task<TopItemResponse> GetTopItems(TopItemRequest request) | |||
| { | |||
| return await _serviceClient.GetTopItems(request); | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,46 @@ | |||
| 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.DTO.TrackByID; | |||
| using GrpcShared.Interfaces; | |||
| using NemAnBlazor.Services.Interfaces; | |||
| using ProtoBuf.Grpc.Client; | |||
| namespace NemAnBlazor.Services | |||
| { | |||
| public class TrackClientService : ITrackClientService | |||
| { | |||
| private ITrackService _serviceClient; | |||
| public TrackClientService(GrpcChannel grpcChannel) | |||
| { | |||
| _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); | |||
| } | |||
| public async Task<TrackResponse> GetById(TrackRequest request) | |||
| { | |||
| return await _serviceClient.GetById(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"> | |||
| <NavLink class="nav-link" href="home" Match="NavLinkMatch.All"> | |||
| <span class="oi oi-home" aria-hidden="true"></span> Home | |||
| </NavLink> | |||
| </div> | |||
| <div class="nav-item px-3"> | |||
| <NavLink class="nav-link" href="profile"> | |||
| <span class="oi oi-plus" aria-hidden="true"></span> Profile | |||
| @* <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="stats"> | |||
| <span class="oi oi-plus" aria-hidden="true"></span> Stats | |||
| <NavLink class="nav-link" href="search"> | |||
| <span class="oi oi-list-rich" aria-hidden="true"></span> Search | |||
| </NavLink> | |||
| </div> | |||
| </nav> | |||
| @@ -0,0 +1,21 @@ | |||
| using Blazored.LocalStorage; | |||
| using GrpcShared.DTO; | |||
| using NemAnBlazor.Services.Interfaces; | |||
| namespace NemAnBlazor | |||
| { | |||
| public static class SpotifyHelper | |||
| { | |||
| public static async Task<string?> TryRefreshToken(IAuthClientService authService, TokenMessage msg, ILocalStorageService localStorage) | |||
| { | |||
| var refreshResponse = await authService.RefreshAccessToken(msg); | |||
| if (refreshResponse.AccessToken != null) | |||
| { | |||
| await localStorage.SetItemAsync<string>("token", refreshResponse.AccessToken); | |||
| return refreshResponse.AccessToken; | |||
| } | |||
| else return null; | |||
| } | |||
| } | |||
| } | |||
| @@ -6,5 +6,7 @@ | |||
| @using Microsoft.AspNetCore.Components.Web.Virtualization | |||
| @using Microsoft.AspNetCore.Components.WebAssembly.Http | |||
| @using Microsoft.JSInterop | |||
| @using NemAnCore | |||
| @using NemAnCore.Shared | |||
| @using NemAnBlazor | |||
| @using NemAnBlazor.Shared | |||
| @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> | |||
| @@ -9,5 +9,6 @@ | |||
| <ItemGroup> | |||
| <PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.1" /> | |||
| <PackageReference Include="protobuf-net.Grpc" Version="1.0.171" /> | |||
| </ItemGroup> | |||
| </Project> | |||
| @@ -0,0 +1,9 @@ | |||
| using System; | |||
| namespace SpotifyService | |||
| { | |||
| public static class GLOBALS | |||
| { | |||
| public const String SPOTIFYURL = "https://api.spotify.com/v1/"; | |||
| public const String MEDIATYPE = "application/json"; | |||
| } | |||
| } | |||
| @@ -0,0 +1,34 @@ | |||
| using Grpc.Net.Client; | |||
| using Microsoft.Net.Http.Headers; | |||
| using Newtonsoft.Json; | |||
| namespace SpotifyService.HttpUtils | |||
| { | |||
| public static class HttpUtils<T> | |||
| { | |||
| public static async Task<T> GetData(HttpClient client, string url, string token) | |||
| { | |||
| //add header | |||
| client.DefaultRequestHeaders.Add(HeaderNames.Authorization, "Bearer " + token); | |||
| //get request | |||
| var req = await client.GetAsync(url); | |||
| //read response | |||
| var response = JsonConvert.DeserializeObject<T>(await req.Content.ReadAsStringAsync())!; | |||
| return response; | |||
| } | |||
| public static async Task PutData(HttpClient client, string url, string token) | |||
| { | |||
| //add header | |||
| client.DefaultRequestHeaders.Add(HeaderNames.Authorization, "Bearer " + token); | |||
| //get request | |||
| await client.PutAsync(url, null); | |||
| } | |||
| } | |||
| } | |||
| @@ -1,17 +1,128 @@ | |||
| using gRPCServer.Services; | |||
| 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; | |||
| using GrpcShared.DTO.Auth; | |||
| var builder = WebApplication.CreateBuilder(args); | |||
| // 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 | |||
| #if DEBUG | |||
| /* | |||
| builder.WebHost.ConfigureKestrel(options => | |||
| { | |||
| options.ListenLocalhost(5050, o => o.Protocols = | |||
| HttpProtocols.Http2); | |||
| options.ListenLocalhost(5051, o => o.Protocols = | |||
| HttpProtocols.Http1AndHttp2); | |||
| }); | |||
| */ | |||
| #endif | |||
| // Add services to the container. | |||
| 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.AddOptions(); | |||
| builder.Services.AddControllersWithViews(); | |||
| builder.Services.AddRazorPages(); | |||
| builder.Services.AddEndpointsApiExplorer(); | |||
| builder.Services.AddSwaggerGen(); | |||
| builder.Services.AddGrpc(); | |||
| builder.Services.AddCodeFirstGrpc(); | |||
| builder.Services.AddCodeFirstGrpcReflection(); | |||
| var app = builder.Build(); | |||
| builder.Services.Configure<CodeRequest>(builder.Configuration.GetSection("AuthParams")); | |||
| app.UseSwagger(); | |||
| app.UseSwaggerUI(); | |||
| // Configure the HTTP request pipeline. | |||
| app.MapGrpcService<GreeterService>(); | |||
| app.MapGet("/", () => "Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909"); | |||
| if (app.Environment.IsDevelopment()) | |||
| { | |||
| app.UseWebAssemblyDebugging(); | |||
| } | |||
| else | |||
| { | |||
| app.UseExceptionHandler("/Error"); | |||
| // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. | |||
| app.UseHsts(); | |||
| } | |||
| app.UseHttpsRedirection(); | |||
| app.UseBlazorFrameworkFiles(); | |||
| app.UseStaticFiles(); | |||
| app.UseRouting(); | |||
| app.UseGrpcWeb(); | |||
| app.MapRazorPages(); | |||
| app.MapControllers(); | |||
| //app.MapGrpcService<AuthService>(); | |||
| //app.MapGrpcService<AuthService>().EnableGrpcWeb(); | |||
| app.MapCodeFirstGrpcReflectionService(); | |||
| app.MapFallbackToFile("index.html"); | |||
| app.Run(); | |||
| @@ -1,10 +1,27 @@ | |||
| { | |||
| "iisSettings": { | |||
| "windowsAuthentication": false, | |||
| "anonymousAuthentication": true, | |||
| "iisExpress": { | |||
| "applicationUrl": "http://localhost:28725", | |||
| "sslPort": 44342 | |||
| } | |||
| }, | |||
| "profiles": { | |||
| "gRPCServer": { | |||
| "IIS Express": { | |||
| "commandName": "IISExpress", | |||
| "launchBrowser": true, | |||
| "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", | |||
| "environmentVariables": { | |||
| "ASPNETCORE_ENVIRONMENT": "Development" | |||
| } | |||
| }, | |||
| "SpotifyService": { | |||
| "commandName": "Project", | |||
| "dotnetRunMessages": true, | |||
| "launchBrowser": false, | |||
| "applicationUrl": "http://localhost:5251;https://localhost:7251", | |||
| "dotnetRunMessages": "true", | |||
| "launchBrowser": true, | |||
| "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", | |||
| "applicationUrl": "https://localhost:5001;http://localhost:5000", | |||
| "environmentVariables": { | |||
| "ASPNETCORE_ENVIRONMENT": "Development" | |||
| } | |||
| @@ -1,21 +0,0 @@ | |||
| syntax = "proto3"; | |||
| option csharp_namespace = "gRPCServer"; | |||
| package greet; | |||
| // The greeting service definition. | |||
| service Greeter { | |||
| // Sends a greeting | |||
| rpc SayHello (HelloRequest) returns (HelloReply); | |||
| } | |||
| // The request message containing the user's name. | |||
| message HelloRequest { | |||
| string name = 1; | |||
| } | |||
| // The response message containing the greetings. | |||
| message HelloReply { | |||
| string message = 1; | |||
| } | |||
| @@ -0,0 +1,136 @@ | |||
| //using IdentityProvider.Protos.AuthService; | |||
| using Blazored.LocalStorage; | |||
| 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.IO; | |||
| using System.Net; | |||
| using System.Net.Http.Headers; | |||
| using System.Text; | |||
| using System.Text.Json; | |||
| namespace SpotifyService.Services | |||
| { | |||
| public class AuthService : IAuthService | |||
| { | |||
| 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; | |||
| _params = options.Value; | |||
| _httpClientFactory = httpClientFactory; | |||
| } | |||
| public async Task<TokenResponse> GetAccessToken(TokenRequest tokenRequest) | |||
| { | |||
| var http = _httpClientFactory.CreateClient("HttpClient"); | |||
| string url = "https://accounts.spotify.com/api/token"; | |||
| http.BaseAddress = new Uri(url); | |||
| //get client id and secret, and redirect uri from appsettings, convert to base64 and set as header | |||
| var secrets = await GetAuthParams(); | |||
| byte[] contentType = Encoding.UTF8.GetBytes($"{secrets.ClientId}:{secrets.ClientSecret}"); | |||
| tokenRequest.RedirectUri = secrets.RedirectURI; | |||
| //AUTHORIZATION HEADER | |||
| http.DefaultRequestHeaders.Add(HeaderNames.Authorization, "Basic " + Convert.ToBase64String(contentType)); | |||
| ////ACCEPT HEADER | |||
| //http.DefaultRequestHeaders.Accept.Add( | |||
| // new MediaTypeWithQualityHeaderValue("application/json")); | |||
| //BODY PARAMS | |||
| var requestBody = new Dictionary<string, string>(); | |||
| requestBody["grant_type"] = tokenRequest.GrantType; | |||
| requestBody["code"] = tokenRequest.Code!; | |||
| requestBody["redirect_uri"] = tokenRequest.RedirectUri!; | |||
| //REQUEST | |||
| var response = await http.PostAsync(url, new FormUrlEncodedContent(requestBody)); | |||
| var contents = JsonConvert.DeserializeObject<TokenResponse>(await response.Content.ReadAsStringAsync()); | |||
| return contents; | |||
| } | |||
| public async Task<CodeRequest> GetAuthParams() | |||
| { | |||
| var authParams = new CodeRequest | |||
| { | |||
| ClientId = _params.ClientId, | |||
| RedirectURI = _params.RedirectURI, | |||
| Scope = _params.Scope, | |||
| ClientSecret = _params.ClientSecret | |||
| }; | |||
| return await Task.FromResult(authParams); | |||
| } | |||
| public async Task<UserInfoResponse> GetUserInfo(TokenMessage tokenM) | |||
| { | |||
| // expired token example "BQBMgFm6jnFNWWeZEMGIRP_f-ENPid7Kw8JubAyuWAe4JK0S1DPFGlaAdZ_Fey6ePkCnz8-cqC0oyRmrciWUy5ISUTQKDe8PTQn4iBRMYCgM0n4GnS1xAErHJcm4Vpu2TAngk-4vQUOfTQRcedNTfCaHKP4uFJgTlTI7JHGrtB-_EZLnFcZ2OQe31oFQIJ1wM3ZtvwnN" | |||
| var http = _httpClientFactory.CreateClient("HttpClient"); | |||
| http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokenM.Token!); | |||
| var response = await http.GetAsync("me"); | |||
| //make this a method in http utils | |||
| if (response.StatusCode == HttpStatusCode.Unauthorized) | |||
| { | |||
| //refresh the token | |||
| var refreshResponse = await RefreshAccessToken(tokenM); | |||
| //if response is invalid redirect to login | |||
| http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", refreshResponse.AccessToken); | |||
| response = await http.GetAsync("me"); | |||
| } | |||
| //var headerError = response.Headers.WwwAuthenticate.; | |||
| var userInfo = JsonConvert.DeserializeObject<UserInfoResponse>(await response.Content.ReadAsStringAsync())!; | |||
| userInfo.ResponseMsg = response.StatusCode; | |||
| return userInfo; | |||
| } | |||
| public async Task<RefreshTokenResponse> RefreshAccessToken(TokenMessage tokenM) | |||
| { | |||
| var client = _httpClientFactory.CreateClient("HttpClient"); | |||
| client.BaseAddress = new Uri("https://accounts.spotify.com/api/token"); | |||
| //BODY PARAMS | |||
| var requestBody = new Dictionary<string, string>(); | |||
| requestBody["refresh_token"] = tokenM.RefreshToken!; | |||
| requestBody["grant_type"] = "refresh_token"; | |||
| var secrets = await GetAuthParams(); | |||
| byte[] contentType = Encoding.UTF8.GetBytes($"{secrets.ClientId}:{secrets.ClientSecret}"); | |||
| //AUTHORIZATION HEADER | |||
| client.DefaultRequestHeaders.Add(HeaderNames.Authorization, "Basic " + Convert.ToBase64String(contentType)); | |||
| //REQUEST | |||
| var response = await client.PostAsync("https://accounts.spotify.com/api/token", new FormUrlEncodedContent(requestBody)); | |||
| if (response.StatusCode == HttpStatusCode.Unauthorized) | |||
| { | |||
| //delete tokens from localstorage | |||
| //redirect to login | |||
| } | |||
| var contents = JsonConvert.DeserializeObject<RefreshTokenResponse>(await response.Content.ReadAsStringAsync())!; | |||
| return contents; | |||
| } | |||
| } | |||
| } | |||
| @@ -1,22 +0,0 @@ | |||
| using Grpc.Core; | |||
| using gRPCServer; | |||
| namespace gRPCServer.Services | |||
| { | |||
| public class GreeterService : Greeter.GreeterBase | |||
| { | |||
| private readonly ILogger<GreeterService> _logger; | |||
| public GreeterService(ILogger<GreeterService> logger) | |||
| { | |||
| _logger = logger; | |||
| } | |||
| public override Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context) | |||
| { | |||
| return Task.FromResult(new HelloReply | |||
| { | |||
| Message = "Hello " + request.Name | |||
| }); | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,49 @@ | |||
| | |||
| using GrpcShared.DTO; | |||
| using GrpcShared.DTO.TopItem; | |||
| using GrpcShared.DTO.Track; | |||
| using GrpcShared.Interfaces; | |||
| using Microsoft.Net.Http.Headers; | |||
| using Newtonsoft.Json; | |||
| using SpotifyService.HttpUtils; | |||
| namespace SpotifyService.Services | |||
| { | |||
| public class StatsService : IStatsService | |||
| { | |||
| private readonly IHttpClientFactory _httpClientFactory; | |||
| public StatsService(IHttpClientFactory httpClientFactory) | |||
| { | |||
| _httpClientFactory = httpClientFactory; | |||
| } | |||
| public async Task<CurrentTrackResponse> GetCurrentlyPlayingTrack(TokenMessage token) | |||
| { | |||
| var client = _httpClientFactory.CreateClient("HttpClient"); | |||
| string url = "me/player/currently-playing"; | |||
| var response = await HttpUtils<CurrentTrackResponse>.GetData(client, url, token.Token!); | |||
| return response; | |||
| } | |||
| public async Task<TopItemResponse> GetTopItems(TopItemRequest request) | |||
| { | |||
| //https://api.spotify.com/v1/me/top/albums?limit=10&offset=5 | |||
| var client = _httpClientFactory.CreateClient("HttpClient"); | |||
| //URL PARAMS | |||
| string url = "me/top/"; | |||
| url += !request.IsTracks ? "artists" : "tracks"; | |||
| url += request.Limit == null ? "" : $"?limit={request.Limit}"; | |||
| if (request.Limit == null && request.Offset != null) url += $"?offset={request.Offset}"; | |||
| else url += request.Offset == null ? "" : $"&offset={request.Offset}"; | |||
| return await HttpUtils<TopItemResponse>.GetData(client, url, request.Token!); | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,104 @@ | |||
| using Google.Rpc; | |||
| using Grpc.Core; | |||
| using GrpcShared; | |||
| using GrpcShared.DTO; | |||
| using GrpcShared.DTO.Search; | |||
| using GrpcShared.DTO.Track.MultipleTrack; | |||
| using GrpcShared.DTO.Track.SaveTracks; | |||
| using GrpcShared.DTO.Track.SingleTrack; | |||
| using GrpcShared.DTO.TrackByID; | |||
| 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"); | |||
| string url = $"search?q={request.Query}&type={request.Type}"; | |||
| return await HttpUtils.HttpUtils<SearchResponse>.GetData(client, url, request.Token!); | |||
| } | |||
| public async Task<SingleTrackResponse> ListSingleTrackAsync(SingleTrackRequest request) | |||
| { | |||
| var client = _httpClientFactory.CreateClient("HttpClient"); | |||
| string url = $"audio-features/{request.Id}"; | |||
| return await HttpUtils.HttpUtils<SingleTrackResponse>.GetData(client, url, request.Token!); | |||
| } | |||
| public async Task<MultipleTrackResponse> ListMultipleTrackAsync(MultipleTrackRequest request) | |||
| { | |||
| var client = _httpClientFactory.CreateClient("HttpClient"); | |||
| var param = new Dictionary<string, List<string>>(); | |||
| param["ids"] = request.Ids!; | |||
| var query = UriUtil(param); | |||
| string url = $"audio-features{query}"; | |||
| return await HttpUtils.HttpUtils<MultipleTrackResponse>.GetData(client,url,request.Token!); | |||
| } | |||
| public async Task SaveTracks(SaveTracksRequest request) | |||
| { | |||
| var client = _httpClientFactory.CreateClient("HttpClient"); | |||
| var param = new Dictionary<string, List<string>>(); | |||
| param["ids"] = request.Ids!; | |||
| var query = UriUtil(param); | |||
| 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 | |||
| await HttpUtils.HttpUtils<StatusCodeMessage>.PutData(client, url, request.Token!); | |||
| } | |||
| 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(); | |||
| } | |||
| public async Task<TrackResponse> GetById(TrackRequest request) | |||
| { | |||
| var client = _httpClientFactory.CreateClient("HttpClient"); | |||
| string url = $"tracks/{request.TrackID}"; | |||
| return await HttpUtils.HttpUtils<TrackResponse>.GetData(client, url, request.Token!); | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,28 @@ | |||
| <Project Sdk="Microsoft.NET.Sdk.Web"> | |||
| <PropertyGroup> | |||
| <TargetFramework>net6.0</TargetFramework> | |||
| <Nullable>enable</Nullable> | |||
| <ImplicitUsings>enable</ImplicitUsings> | |||
| </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" /> | |||
| <PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" /> | |||
| </ItemGroup> | |||
| <ItemGroup> | |||
| <ProjectReference Include="..\GrpcShared\GrpcShared.csproj" /> | |||
| <ProjectReference Include="..\NemAnCore\NemAnBlazor.csproj" /> | |||
| </ItemGroup> | |||
| </Project> | |||
| @@ -8,7 +8,13 @@ | |||
| "AllowedHosts": "*", | |||
| "Kestrel": { | |||
| "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" | |||
| } | |||
| } | |||
| @@ -1,17 +0,0 @@ | |||
| <Project Sdk="Microsoft.NET.Sdk.Web"> | |||
| <PropertyGroup> | |||
| <TargetFramework>net6.0</TargetFramework> | |||
| <Nullable>enable</Nullable> | |||
| <ImplicitUsings>enable</ImplicitUsings> | |||
| </PropertyGroup> | |||
| <ItemGroup> | |||
| <Protobuf Include="Protos\greet.proto" GrpcServices="Server" /> | |||
| </ItemGroup> | |||
| <ItemGroup> | |||
| <PackageReference Include="Grpc.AspNetCore" Version="2.40.0" /> | |||
| </ItemGroup> | |||
| </Project> | |||