Browse Source

Merge branch 'release/1.1.0'

tags/v1.1.0^0
anastasijasavov 3 years ago
parent
commit
9bc41c2b33
64 changed files with 2002 additions and 194 deletions
  1. 28
    0
      GrpcShared/DTO/Auth/CodeRequest.cs
  2. 26
    0
      GrpcShared/DTO/Auth/TokenRequest.cs
  3. 24
    0
      GrpcShared/DTO/Auth/TokenResponse.cs
  4. 131
    0
      GrpcShared/DTO/Search/SearchDetails.cs
  5. 21
    0
      GrpcShared/DTO/Search/SearchRequest.cs
  6. 127
    0
      GrpcShared/DTO/Search/SearchResponse.cs
  7. 29
    0
      GrpcShared/DTO/TokenMessage.cs
  8. 24
    0
      GrpcShared/DTO/TopItem/TopItemRequest.cs
  9. 63
    0
      GrpcShared/DTO/TopItem/TopItemResponse.cs
  10. 90
    0
      GrpcShared/DTO/Track/CurrentTrackResponse.cs
  11. 17
    0
      GrpcShared/DTO/Track/MultipleTrack/MultipleTrackRequest.cs
  12. 74
    0
      GrpcShared/DTO/Track/MultipleTrack/MultipleTrackResponse.cs
  13. 17
    0
      GrpcShared/DTO/Track/SaveTracks/SaveTracksRequest.cs
  14. 17
    0
      GrpcShared/DTO/Track/SingleTrack/SingleTrackRequest.cs
  15. 62
    0
      GrpcShared/DTO/Track/SingleTrack/SingleTrackResponse.cs
  16. 19
    0
      GrpcShared/DTO/Track/TrackByID/TrackRequest.cs
  17. 39
    0
      GrpcShared/DTO/Track/TrackByID/TrackResponse.cs
  18. 19
    0
      GrpcShared/DTO/User/UserInfoResponse.cs
  19. 9
    0
      GrpcShared/GLOBALS.cs
  20. 21
    0
      GrpcShared/GrpcShared.csproj
  21. 16
    0
      GrpcShared/Interfaces/IAuthService.cs
  22. 19
    0
      GrpcShared/Interfaces/IStatsService.cs
  23. 24
    0
      GrpcShared/Interfaces/ITrackService.cs
  24. 27
    0
      IdentityProvider/IdentityProvider.csproj
  25. 81
    0
      IdentityProvider/Program.cs
  26. 30
    0
      IdentityProvider/Properties/launchSettings.json
  27. 106
    0
      IdentityProvider/Services/AuthService.cs
  28. 8
    0
      IdentityProvider/appsettings.Development.json
  29. 20
    0
      IdentityProvider/appsettings.json
  30. 14
    8
      NemAn.sln
  31. 9
    2
      NemAnCore/App.razor
  32. 31
    0
      NemAnCore/NemAnBlazor.csproj
  33. 0
    14
      NemAnCore/NemAnCore.csproj
  34. 33
    0
      NemAnCore/Pages/Callback.razor
  35. 0
    18
      NemAnCore/Pages/Counter.razor
  36. 33
    44
      NemAnCore/Pages/FetchData.razor
  37. 27
    0
      NemAnCore/Pages/Home.razor
  38. 0
    9
      NemAnCore/Pages/Index.razor
  39. 52
    0
      NemAnCore/Pages/Login.razor
  40. 7
    0
      NemAnCore/Pages/LoginRedirect.razor
  41. 25
    1
      NemAnCore/Program.cs
  42. 2
    16
      NemAnCore/Properties/launchSettings.json
  43. 92
    0
      NemAnCore/Services/AuthClientService.cs
  44. 14
    0
      NemAnCore/Services/Interfaces/IAuthClientService.cs
  45. 12
    0
      NemAnCore/Services/Interfaces/IStatsClientService.cs
  46. 17
    0
      NemAnCore/Services/Interfaces/ITrackClientService.cs
  47. 28
    0
      NemAnCore/Services/StatsClientService.cs
  48. 46
    0
      NemAnCore/Services/TrackClientService.cs
  49. 1
    1
      NemAnCore/Shared/MainLayout.razor
  50. 8
    8
      NemAnCore/Shared/NavMenu.razor
  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. 114
    5
      gRPCServer/Program.cs
  57. 21
    4
      gRPCServer/Properties/launchSettings.json
  58. 0
    21
      gRPCServer/Protos/greet.proto
  59. 0
    22
      gRPCServer/Services/GreeterService.cs
  60. 47
    0
      gRPCServer/Services/StatsService.cs
  61. 103
    0
      gRPCServer/Services/TrackService.cs
  62. 28
    0
      gRPCServer/SpotifyService.csproj
  63. 1
    1
      gRPCServer/appsettings.json
  64. 0
    17
      gRPCServer/gRPCServer.csproj

+ 28
- 0
GrpcShared/DTO/Auth/CodeRequest.cs View File

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

+ 26
- 0
GrpcShared/DTO/Auth/TokenRequest.cs View File

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

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

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

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

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

+ 29
- 0
GrpcShared/DTO/TokenMessage.cs View File

@@ -0,0 +1,29 @@
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; }
}
}

+ 24
- 0
GrpcShared/DTO/TopItem/TopItemRequest.cs View File

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

@@ -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
{
[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 View File

@@ -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
{
[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 View File

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

@@ -0,0 +1,74 @@
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GrpcShared.DTO.Track.MultipleTrack
{
[ProtoContract]
public class MultipleTrackResponse
{
[ProtoMember(1)]
public List<AudioFeature>? Audio_Features { get; set; }
}
[ProtoContract]
public class AudioFeature
{
[ProtoMember(1)]
public 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 View File

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

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

@@ -0,0 +1,62 @@
using ProtoBuf;

namespace GrpcShared.DTO.Track.SingleTrack
{
[ProtoContract]
public class SingleTrackResponse
{
[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 View File

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

@@ -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
{
[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 View File

@@ -0,0 +1,19 @@
using Newtonsoft.Json;
using ProtoBuf;

namespace GrpcShared.DTO.User
{
[ProtoContract]
public class UserInfoResponse
{
[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 View File

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

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

+ 16
- 0
GrpcShared/Interfaces/IAuthService.cs View File

@@ -0,0 +1,16 @@
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<ClientSecrets> GetClientSecrets();
}
}

+ 19
- 0
GrpcShared/Interfaces/IStatsService.cs View File

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

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

+ 27
- 0
IdentityProvider/IdentityProvider.csproj View File

@@ -0,0 +1,27 @@
<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>

</Project>

+ 81
- 0
IdentityProvider/Program.cs View File

@@ -0,0 +1,81 @@
using GrpcShared;
using IdentityProvider.Services;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using ProtoBuf.Grpc.Server;
using Microsoft.Extensions.Options;
using GrpcShared.DTO.Auth;
using SpotifyService.Services;
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.Configure<CodeRequest>(builder.Configuration.GetSection("AuthParams"));
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();

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 View File

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

+ 106
- 0
IdentityProvider/Services/AuthService.cs View File

@@ -0,0 +1,106 @@
//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.IdentityModel.Tokens.Jwt;
using System.IO;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;

namespace IdentityProvider.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();

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)
{
//var des = JsonConvert.DeserializeObject<TokenMessage>(tokenM);
//var tokenStorage = _sessionStorageService.GetItemAsync<string>("token");

//hc.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authToken);
//var response = hc.GetAsync(userInfoUrl).Result;
//dynamic userInfo = response.Content.ReadAsAsync().Result;
//return userInfo;

var http = _httpClientFactory.CreateClient();
http.BaseAddress = new Uri("https://api.spotify.com/v1/me");
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokenM.Token);
var response = http.GetAsync(http.BaseAddress).Result;
var userInfo = JsonConvert.DeserializeObject<UserInfoResponse>(await response.Content.ReadAsStringAsync())!;
return userInfo;

//http.DefaultRequestHeaders.Add(HeaderNames.Authorization, "Bearer " + token.Token);

//var response = await http.GetAsync(http.BaseAddress + "me");

//var user = JsonConvert.DeserializeObject<UserInfoResponse>(await response.Content.ReadAsStringAsync());

//return await Task.FromResult(user!);
}
}
}

+ 8
- 0
IdentityProvider/appsettings.Development.json View File

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

+ 20
- 0
IdentityProvider/appsettings.json View File

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

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

+ 9
- 2
NemAnCore/App.razor View File

@@ -1,6 +1,12 @@
<Router AppAssembly="@typeof(App).Assembly">
@using NemAnBlazor.Pages
<CascadingAuthenticationState>
<Router AppAssembly="@typeof(App).Assembly">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
<AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" >
<Authorizing>
<text>Please wait, we are authorizint the user.</text>
</Authorizing>
</AuthorizeRouteView>
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
</Found>
<NotFound>
@@ -10,3 +16,4 @@
</LayoutView>
</NotFound>
</Router>
</CascadingAuthenticationState>

+ 31
- 0
NemAnCore/NemAnBlazor.csproj View File

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

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

@@ -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
- 18
NemAnCore/Pages/Counter.razor View File

@@ -1,18 +0,0 @@
@page "/counter"

<PageTitle>Counter</PageTitle>

<h1>Counter</h1>

<p role="status">Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
private int currentCount = 0;

private void IncrementCount()
{
currentCount++;
}
}

+ 33
- 44
NemAnCore/Pages/FetchData.razor View File

@@ -1,57 +1,46 @@
@page "/fetchdata"
@inject HttpClient Http
@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>Weather forecast</PageTitle>

<h1>Weather forecast</h1>
<PageTitle>Search</PageTitle>

<h1>Search</h1>

<button class="btn btn-primary" @onclick="Click">Click me</button>

<p>This component demonstrates fetching data from the server.</p>

@if (forecasts == null)
{
<p><em>Loading...</em></p>
}
else
{
<table class="table">
<thead>
<tr>
<th>Date</th>
<th>Temp. (C)</th>
<th>Temp. (F)</th>
<th>Summary</th>
</tr>
</thead>
<tbody>
@foreach (var forecast in forecasts)
{
<tr>
<td>@forecast.Date.ToShortDateString()</td>
<td>@forecast.TemperatureC</td>
<td>@forecast.TemperatureF</td>
<td>@forecast.Summary</td>
</tr>
}
</tbody>
</table>
}

@code {
private WeatherForecast[]? forecasts;
//protected override async Task OnInitializedAsync()
//{
//MultipleTrackRequest mreq = new() { Ids = new List<string>(){"3JAeYOjyJodI4PRs44lx2l", "6clZa1yrZe7pJrYFUcD9KW"}, Token = token };
//MultipleTrackResponse multipleTrackResponse = await SearchService.GetListMultipleTrackAsync(mreq);

protected override async Task OnInitializedAsync()
{
forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("sample-data/weather.json");
}
//SingleTrackRequest singleTrackRequest = new() { Id = "3JAeYOjyJodI4PRs44lx2l", Token = token };
//SingleTrackResponse singleTrackResponse = await SearchService.GetListSingleTrackAsync(singleTrackRequest);
//}

public class WeatherForecast
private async Task Click()
{
public DateTime Date { get; set; }

public int TemperatureC { get; set; }

public string? Summary { get; set; }
var token = await localStorage.GetItemAsync<string>("token");
TokenMessage tm = new() { Token = token };
SearchRequest request = new() { Query = "aitch", Type = "track", Token = token };
SearchResponse searchResponse = await SearchService.GetListSearchAsync(request);

public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}


}

+ 27
- 0
NemAnCore/Pages/Home.razor View File

@@ -0,0 +1,27 @@
@page "/home"
@using GrpcShared.DTO
@using GrpcShared.DTO.Track
@using NemAnBlazor.Services.Interfaces
@inject Blazored.LocalStorage.ILocalStorageService localStorage
@inject IStatsClientService spotifyService
@inject ITrackClientService trackService

<h3>Home</h3>

<p>login radi</p>

@code {
protected override async Task OnInitializedAsync()
{
string tokenS = await localStorage.GetItemAsync<string>("token");
TokenMessage token = new TokenMessage{Token = tokenS};

CurrentTrackResponse response = await spotifyService.GetCurrentlyPlayingTrack(token);

//napravi komponentu koja ce da prikazuje sta trenutno slusas i passuj joj parametre

//4fy1A2WBTPX55mUI16TQXa
//var trackById = await trackService.GetById(new GrpcShared.DTO.TrackByID.TrackRequest { TrackID = "4fy1A2WBTPX55mUI16TQXa", Token = tokenS });
var items = await spotifyService.GetTopItems(new GrpcShared.DTO.TopItem.TopItemRequest { Token = tokenS, IsTracks = false, Offset = 5});
}
}

+ 0
- 9
NemAnCore/Pages/Index.razor View File

@@ -1,9 +0,0 @@
@page "/"

<PageTitle>Index</PageTitle>

<h1>Hello, world!</h1>

Welcome to your new app.

<SurveyPrompt Title="How is Blazor working for you?" />

+ 52
- 0
NemAnCore/Pages/Login.razor View File

@@ -0,0 +1,52 @@
@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>
Dobrodosli @context.User.Claims.FirstOrDefault(x => x.Type == "name")?.Value.ToUpper()
</Authorized>
<NotAuthorized>
Nisi autorizovan.
<button class="btn btn-primary" @onclick="LoginUser">Autorizuj</button>
</NotAuthorized>
</AuthorizeView>

<PageTitle>Index</PageTitle>

<h1>Pozdrav Diligent!</h1>


Dobrodošli u našu NemAn aplikaciju.




@code {
private string? message;

protected override async Task OnInitializedAsync()
{
message = "Cao";
}

private async Task 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 View File

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

+ 25
- 1
NemAnCore/Program.cs View File

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

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


+ 92
- 0
NemAnCore/Services/AuthClientService.cs View File

@@ -0,0 +1,92 @@
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 _sessionStorage;
public AuthClientService(GrpcChannel grpcChannel, ILocalStorageService sessionStorage)
{
_serviceClient = grpcChannel.CreateGrpcService<IAuthService>();
_sessionStorage = sessionStorage;
}
public async Task<TokenResponse> GetAccessToken(TokenRequest request)
{
return await _serviceClient.GetAccessToken(request);
}

//public override async Task<AuthenticationState> GetAuthenticationStateAsync()
//{
// string token = await _sessionStorage.GetItemAsync<string>("token");

// //token = "BQBMgFm6jnFNWWeZEMGIRP_f-ENPid7Kw8JubAyuWAe4JK0S1DPFGlaAdZ_Fey6ePkCnz8-cqC0oyRmrciWUy5ISUTQKDe8PTQn4iBRMYCgM0n4GnS1xAErHJcm4Vpu2TAngk-4vQUOfTQRcedNTfCaHKP4uFJgTlTI7JHGrtB-_EZLnFcZ2OQe31oFQIJ1wM3ZtvwnN";
// if (token == null) return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()));
// var userInfo = await _serviceClient.GetUserInfo(new TokenMessage ( token ));

// 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()
{
await Task.Delay(1500);

string token = await _sessionStorage.GetItemAsync<string>("token");

//token = "BQBMgFm6jnFNWWeZEMGIRP_f-ENPid7Kw8JubAyuWAe4JK0S1DPFGlaAdZ_Fey6ePkCnz8-cqC0oyRmrciWUy5ISUTQKDe8PTQn4iBRMYCgM0n4GnS1xAErHJcm4Vpu2TAngk-4vQUOfTQRcedNTfCaHKP4uFJgTlTI7JHGrtB-_EZLnFcZ2OQe31oFQIJ1wM3ZtvwnN";
if (token == null) return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()));

var userInfo = await _serviceClient.GetUserInfo(new TokenMessage{Token = token});

List<Claim> claims = new();

claims.Add(new Claim("email", userInfo.Email!));
claims.Add(new Claim("id", userInfo.Id!));
claims.Add(new Claim("name", userInfo.DisplayName!));

ClaimsIdentity identity = new(claims, "jwt");
//ClaimsIdentity identity = new();
ClaimsPrincipal user = new(identity);
AuthenticationState state = new(user);

NotifyAuthenticationStateChanged(Task.FromResult(state));

return state;
}
}
}

+ 14
- 0
NemAnCore/Services/Interfaces/IAuthClientService.cs View File

@@ -0,0 +1,14 @@
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);
}
}

+ 12
- 0
NemAnCore/Services/Interfaces/IStatsClientService.cs View File

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

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

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

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

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

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


+ 8
- 8
NemAnCore/Shared/NavMenu.razor View File

@@ -1,27 +1,27 @@
<div class="top-row ps-3 navbar navbar-dark">
<div class="container-fluid">
<a class="navbar-brand" href="">NemAnCore</a>
<a class="navbar-brand" href="">Spotify</a>
<button title="Navigation menu" class="navbar-toggler" @onclick="ToggleNavMenu">
<span class="navbar-toggler-icon"></span>
</button>
</div>
</div>

<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
<nav class="flex-column">
<div class="@NavMenuCssClass" @onclick="ToggleNavMenu" >
<nav class="flex-column" >
<div class="nav-item px-3">
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
<span class="oi oi-home" aria-hidden="true"></span> Home
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link" href="counter">
@* <div class="nav-item px-3">
<NavLink class="nav-link" href="callback">
<span class="oi oi-plus" aria-hidden="true"></span> Counter
</NavLink>
</div>
</div>*@
<div class="nav-item px-3">
<NavLink class="nav-link" href="fetchdata">
<span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
<NavLink class="nav-link" href="search">
<span class="oi oi-list-rich" aria-hidden="true"></span> Search
</NavLink>
</div>
</nav>

+ 4
- 2
NemAnCore/_Imports.razor View File

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

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

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

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

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

}

}
}

+ 114
- 5
gRPCServer/Program.cs View File

@@ -1,17 +1,126 @@
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;

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.AddControllersWithViews();
builder.Services.AddRazorPages();

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

builder.Services.AddGrpc();
builder.Services.AddCodeFirstGrpc();
builder.Services.AddCodeFirstGrpcReflection();

var app = builder.Build();

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 View File

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

@@ -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
- 22
gRPCServer/Services/GreeterService.cs View File

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

+ 47
- 0
gRPCServer/Services/StatsService.cs View File

@@ -0,0 +1,47 @@

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

return await HttpUtils<CurrentTrackResponse>.GetData(client, url, token.Token!);

}

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

}
}
}

+ 103
- 0
gRPCServer/Services/TrackService.cs View File

@@ -0,0 +1,103 @@
using Google.Rpc;
using Grpc.Core;
using GrpcShared;
using GrpcShared.DTO.Search;
using GrpcShared.DTO.Track.MultipleTrack;
using GrpcShared.DTO.Track.SaveTracks;
using GrpcShared.DTO.Track.SingleTrack;
using GrpcShared.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<MultipleTrackResponse>.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 View File

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

+ 1
- 1
gRPCServer/appsettings.json View File

@@ -8,7 +8,7 @@
"AllowedHosts": "*",
"Kestrel": {
"EndpointDefaults": {
"Protocols": "Http2"
"Protocols": "Http1AndHttp2"
}
}
}

+ 0
- 17
gRPCServer/gRPCServer.csproj View File

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

Loading…
Cancel
Save