Procházet zdrojové kódy

Pull dev branch

feature/blazor-frontend
nemanja.grkovic před 3 roky
rodič
revize
1ac753ff45
65 změnil soubory, kde provedl 2199 přidání a 125 odebrání
  1. 28
    0
      GrpcShared/DTO/Auth/CodeRequest.cs
  2. 22
    0
      GrpcShared/DTO/Auth/RefreshTokenResponse.cs
  3. 26
    0
      GrpcShared/DTO/Auth/TokenRequest.cs
  4. 24
    0
      GrpcShared/DTO/Auth/TokenResponse.cs
  5. 131
    0
      GrpcShared/DTO/Search/SearchDetails.cs
  6. 21
    0
      GrpcShared/DTO/Search/SearchRequest.cs
  7. 127
    0
      GrpcShared/DTO/Search/SearchResponse.cs
  8. 23
    0
      GrpcShared/DTO/StatusCodeMessage.cs
  9. 31
    0
      GrpcShared/DTO/TokenMessage.cs
  10. 24
    0
      GrpcShared/DTO/TopItem/TopItemRequest.cs
  11. 63
    0
      GrpcShared/DTO/TopItem/TopItemResponse.cs
  12. 90
    0
      GrpcShared/DTO/Track/CurrentTrackResponse.cs
  13. 17
    0
      GrpcShared/DTO/Track/MultipleTrack/MultipleTrackRequest.cs
  14. 74
    0
      GrpcShared/DTO/Track/MultipleTrack/MultipleTrackResponse.cs
  15. 17
    0
      GrpcShared/DTO/Track/SaveTracks/SaveTracksRequest.cs
  16. 17
    0
      GrpcShared/DTO/Track/SingleTrack/SingleTrackRequest.cs
  17. 62
    0
      GrpcShared/DTO/Track/SingleTrack/SingleTrackResponse.cs
  18. 19
    0
      GrpcShared/DTO/Track/TrackByID/TrackRequest.cs
  19. 39
    0
      GrpcShared/DTO/Track/TrackByID/TrackResponse.cs
  20. 19
    0
      GrpcShared/DTO/User/UserInfoResponse.cs
  21. 9
    0
      GrpcShared/GLOBALS.cs
  22. 21
    0
      GrpcShared/GrpcShared.csproj
  23. 17
    0
      GrpcShared/Interfaces/IAuthService.cs
  24. 19
    0
      GrpcShared/Interfaces/IStatsService.cs
  25. 24
    0
      GrpcShared/Interfaces/ITrackService.cs
  26. 31
    0
      IdentityProvider/IdentityProvider.csproj
  27. 80
    0
      IdentityProvider/Program.cs
  28. 30
    0
      IdentityProvider/Properties/launchSettings.json
  29. 8
    0
      IdentityProvider/appsettings.Development.json
  30. 20
    0
      IdentityProvider/appsettings.json
  31. 14
    8
      NemAn.sln
  32. 7
    2
      NemAnCore/App.razor
  33. 31
    0
      NemAnCore/NemAnBlazor.csproj
  34. 0
    14
      NemAnCore/NemAnCore.csproj
  35. 33
    0
      NemAnCore/Pages/Callback.razor
  36. 67
    0
      NemAnCore/Pages/FetchData.razor
  37. 72
    0
      NemAnCore/Pages/Home.razor
  38. 49
    0
      NemAnCore/Pages/Login.razor
  39. 7
    0
      NemAnCore/Pages/LoginRedirect.razor
  40. 25
    1
      NemAnCore/Program.cs
  41. 2
    16
      NemAnCore/Properties/launchSettings.json
  42. 99
    0
      NemAnCore/Services/AuthClientService.cs
  43. 15
    0
      NemAnCore/Services/Interfaces/IAuthClientService.cs
  44. 12
    0
      NemAnCore/Services/Interfaces/IStatsClientService.cs
  45. 17
    0
      NemAnCore/Services/Interfaces/ITrackClientService.cs
  46. 28
    0
      NemAnCore/Services/StatsClientService.cs
  47. 46
    0
      NemAnCore/Services/TrackClientService.cs
  48. 1
    1
      NemAnCore/Shared/MainLayout.razor
  49. 10
    10
      NemAnCore/Shared/NavMenu.razor
  50. 21
    0
      NemAnCore/SpotifyHelper.cs
  51. 4
    2
      NemAnCore/_Imports.razor
  52. 1
    1
      NemAnCore/wwwroot/index.html
  53. 1
    0
      SpotifyWorker/SpotifyWorker.csproj
  54. 9
    0
      gRPCServer/GLOBALS.cs
  55. 34
    0
      gRPCServer/HttpUtils/HttpUtils.cs
  56. 116
    5
      gRPCServer/Program.cs
  57. 21
    4
      gRPCServer/Properties/launchSettings.json
  58. 0
    21
      gRPCServer/Protos/greet.proto
  59. 136
    0
      gRPCServer/Services/AuthService.cs
  60. 0
    22
      gRPCServer/Services/GreeterService.cs
  61. 49
    0
      gRPCServer/Services/StatsService.cs
  62. 104
    0
      gRPCServer/Services/TrackService.cs
  63. 28
    0
      gRPCServer/SpotifyService.csproj
  64. 7
    1
      gRPCServer/appsettings.json
  65. 0
    17
      gRPCServer/gRPCServer.csproj

+ 28
- 0
GrpcShared/DTO/Auth/CodeRequest.cs Zobrazit soubor

@@ -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;
}
}

+ 22
- 0
GrpcShared/DTO/Auth/RefreshTokenResponse.cs Zobrazit soubor

@@ -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; }
}
}

+ 26
- 0
GrpcShared/DTO/Auth/TokenRequest.cs Zobrazit soubor

@@ -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; }

}
}

+ 24
- 0
GrpcShared/DTO/Auth/TokenResponse.cs Zobrazit soubor

@@ -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; }
}
}

+ 131
- 0
GrpcShared/DTO/Search/SearchDetails.cs Zobrazit soubor

@@ -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; }
}
}
}

+ 21
- 0
GrpcShared/DTO/Search/SearchRequest.cs Zobrazit soubor

@@ -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; }
}
}

+ 127
- 0
GrpcShared/DTO/Search/SearchResponse.cs Zobrazit soubor

@@ -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; }
}
}

+ 23
- 0
GrpcShared/DTO/StatusCodeMessage.cs Zobrazit soubor

@@ -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; }
}
}

+ 31
- 0
GrpcShared/DTO/TokenMessage.cs Zobrazit soubor

@@ -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; }
}
}

+ 24
- 0
GrpcShared/DTO/TopItem/TopItemRequest.cs Zobrazit soubor

@@ -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; }
}
}

+ 63
- 0
GrpcShared/DTO/TopItem/TopItemResponse.cs Zobrazit soubor

@@ -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; }
}
}

+ 90
- 0
GrpcShared/DTO/Track/CurrentTrackResponse.cs Zobrazit soubor

@@ -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; }
}
}

+ 17
- 0
GrpcShared/DTO/Track/MultipleTrack/MultipleTrackRequest.cs Zobrazit soubor

@@ -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; }
}
}

+ 74
- 0
GrpcShared/DTO/Track/MultipleTrack/MultipleTrackResponse.cs Zobrazit soubor

@@ -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; }
}
}

+ 17
- 0
GrpcShared/DTO/Track/SaveTracks/SaveTracksRequest.cs Zobrazit soubor

@@ -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; }
}
}

+ 17
- 0
GrpcShared/DTO/Track/SingleTrack/SingleTrackRequest.cs Zobrazit soubor

@@ -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; }
}
}

+ 62
- 0
GrpcShared/DTO/Track/SingleTrack/SingleTrackResponse.cs Zobrazit soubor

@@ -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; }
}
}

+ 19
- 0
GrpcShared/DTO/Track/TrackByID/TrackRequest.cs Zobrazit soubor

@@ -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; }
}
}

+ 39
- 0
GrpcShared/DTO/Track/TrackByID/TrackResponse.cs Zobrazit soubor

@@ -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; }
}
}

+ 19
- 0
GrpcShared/DTO/User/UserInfoResponse.cs Zobrazit soubor

@@ -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; }
}
}

+ 9
- 0
GrpcShared/GLOBALS.cs Zobrazit soubor

@@ -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";
}
}

+ 21
- 0
GrpcShared/GrpcShared.csproj Zobrazit soubor

@@ -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>

+ 17
- 0
GrpcShared/Interfaces/IAuthService.cs Zobrazit soubor

@@ -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();
}
}

+ 19
- 0
GrpcShared/Interfaces/IStatsService.cs Zobrazit soubor

@@ -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);
}
}

+ 24
- 0
GrpcShared/Interfaces/ITrackService.cs Zobrazit soubor

@@ -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);
}
}

+ 31
- 0
IdentityProvider/IdentityProvider.csproj Zobrazit soubor

@@ -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>

+ 80
- 0
IdentityProvider/Program.cs Zobrazit soubor

@@ -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();

+ 30
- 0
IdentityProvider/Properties/launchSettings.json Zobrazit soubor

@@ -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"
}
}
}
}

+ 8
- 0
IdentityProvider/appsettings.Development.json Zobrazit soubor

@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

+ 20
- 0
IdentityProvider/appsettings.json Zobrazit soubor

@@ -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"
}
}

+ 14
- 8
NemAn.sln Zobrazit soubor

@@ -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

+ 7
- 2
NemAnCore/App.razor Zobrazit soubor

@@ -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>

+ 31
- 0
NemAnCore/NemAnBlazor.csproj Zobrazit soubor

@@ -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>

+ 0
- 14
NemAnCore/NemAnCore.csproj Zobrazit soubor

@@ -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>

+ 33
- 0
NemAnCore/Pages/Callback.razor Zobrazit soubor

@@ -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");
}
}

+ 67
- 0
NemAnCore/Pages/FetchData.razor Zobrazit soubor

@@ -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;
}

}


}

+ 72
- 0
NemAnCore/Pages/Home.razor Zobrazit soubor

@@ -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;
}


}
}

+ 49
- 0
NemAnCore/Pages/Login.razor Zobrazit soubor

@@ -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);

}
}

+ 7
- 0
NemAnCore/Pages/LoginRedirect.razor Zobrazit soubor

@@ -0,0 +1,7 @@
@inject NavigationManager UriHelper
@code {
protected override void OnInitialized()
{
UriHelper.NavigateTo("/");
}
}

+ 25
- 1
NemAnCore/Program.cs Zobrazit soubor

@@ -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();


+ 2
- 16
NemAnCore/Properties/launchSettings.json Zobrazit soubor

@@ -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"
}
}
}
}


+ 99
- 0
NemAnCore/Services/AuthClientService.cs Zobrazit soubor

@@ -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);
}
}
}

+ 15
- 0
NemAnCore/Services/Interfaces/IAuthClientService.cs Zobrazit soubor

@@ -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);
}
}

+ 12
- 0
NemAnCore/Services/Interfaces/IStatsClientService.cs Zobrazit soubor

@@ -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);
}
}

+ 17
- 0
NemAnCore/Services/Interfaces/ITrackClientService.cs Zobrazit soubor

@@ -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);
}
}

+ 28
- 0
NemAnCore/Services/StatsClientService.cs Zobrazit soubor

@@ -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);
}
}
}

+ 46
- 0
NemAnCore/Services/TrackClientService.cs Zobrazit soubor

@@ -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
- 1
NemAnCore/Shared/MainLayout.razor Zobrazit soubor

@@ -1,7 +1,7 @@
@inherits LayoutComponentBase

<div class="page">
<div class="sidebar">
<div style="background: green ;" class="sidebar">
<NavMenu />
</div>


+ 10
- 10
NemAnCore/Shared/NavMenu.razor Zobrazit soubor

@@ -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>

+ 21
- 0
NemAnCore/SpotifyHelper.cs Zobrazit soubor

@@ -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;
}
}
}

+ 4
- 2
NemAnCore/_Imports.razor Zobrazit soubor

@@ -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

+ 1
- 1
NemAnCore/wwwroot/index.html Zobrazit soubor

@@ -8,7 +8,7 @@
<base href="/" />
<link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />
<link href="css/app.css" rel="stylesheet" />
<link href="NemAnCore.styles.css" rel="stylesheet" />
<link href="NemAnBlazor.styles.css" rel="stylesheet" />
</head>

<body>

+ 1
- 0
SpotifyWorker/SpotifyWorker.csproj Zobrazit soubor

@@ -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>

+ 9
- 0
gRPCServer/GLOBALS.cs Zobrazit soubor

@@ -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";
}
}

+ 34
- 0
gRPCServer/HttpUtils/HttpUtils.cs Zobrazit soubor

@@ -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);

}

}
}

+ 116
- 5
gRPCServer/Program.cs Zobrazit soubor

@@ -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();

+ 21
- 4
gRPCServer/Properties/launchSettings.json Zobrazit soubor

@@ -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"
}

+ 0
- 21
gRPCServer/Protos/greet.proto Zobrazit soubor

@@ -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;
}

+ 136
- 0
gRPCServer/Services/AuthService.cs Zobrazit soubor

@@ -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;
}
}
}

+ 0
- 22
gRPCServer/Services/GreeterService.cs Zobrazit soubor

@@ -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
});
}
}
}

+ 49
- 0
gRPCServer/Services/StatsService.cs Zobrazit soubor

@@ -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!);

}
}
}

+ 104
- 0
gRPCServer/Services/TrackService.cs Zobrazit soubor

@@ -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!);
}
}
}


+ 28
- 0
gRPCServer/SpotifyService.csproj Zobrazit soubor

@@ -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>

+ 7
- 1
gRPCServer/appsettings.json Zobrazit soubor

@@ -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"
}
}

+ 0
- 17
gRPCServer/gRPCServer.csproj Zobrazit soubor

@@ -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>

Načítá se…
Zrušit
Uložit