adding db update on token refresh, more efficient walking, adding playlist generator

This commit is contained in:
Andy Pack 2024-01-21 20:43:46 +00:00
parent 5f535c0929
commit b700d94afb
Signed by: sarsoo
GPG Key ID: A55BA3536A5E0ED7
12 changed files with 108 additions and 29 deletions

View File

@ -8,6 +8,6 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Mixonomer.Fire\Mixonomer.Fire.csproj" /> <ProjectReference Include="..\Mixonomer.Fire\Mixonomer.Fire.csproj" />
<ProjectReference Include="..\Mixonomer.Playlist\Mixonomer.Playlist.csproj" /> <ProjectReference Include="..\Mixonomer\Mixonomer.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -2,8 +2,10 @@
using System.Linq; using System.Linq;
using Mixonomer.Fire; using Mixonomer.Fire;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Mixonomer.Fire.Extensions; using Mixonomer.Fire.Extensions;
using Mixonomer.Playlist; using Mixonomer;
namespace Mixonomer.CLI; namespace Mixonomer.CLI;
@ -19,5 +21,11 @@ class Program
var walker = new PartTreeWalker(repo); var walker = new PartTreeWalker(repo);
var partPlaylists = await walker.GetPlaylistParts("andy", "RAP"); var partPlaylists = await walker.GetPlaylistParts("andy", "RAP");
var spotifyNetwork = new SpotifyNetworkProvider(repo, null, NullLogger<SpotifyNetworkProvider>.Instance);
var generator = new PlaylistGenerator(repo, spotifyNetwork, walker, NullLogger<PlaylistGenerator>.Instance);
await generator.GeneratePlaylist("RAP", "andy");
} }
} }

View File

@ -15,7 +15,7 @@ public class UserRepo
public UserRepo(FirestoreDb db = null, string projectId = null) public UserRepo(FirestoreDb db = null, string projectId = null)
{ {
this.db = db ?? FirestoreDb.Create(projectId); this.db = db ?? FirestoreDb.Create(projectId ?? Environment.GetEnvironmentVariable("GOOGLE_CLOUD_PROJECT"));
userCollection = this.db.Collection(USER_COLLECTION); userCollection = this.db.Collection(USER_COLLECTION);
} }

View File

@ -15,7 +15,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Mixonomer.Fire\Mixonomer.Fire.csproj" /> <ProjectReference Include="..\Mixonomer.Fire\Mixonomer.Fire.csproj" />
<ProjectReference Include="..\Mixonomer.Playlist\Mixonomer.Playlist.csproj" /> <ProjectReference Include="..\Mixonomer\Mixonomer.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Mixonomer.Fire; using Mixonomer.Fire;
using SpotifyAPI.Web;
namespace Mixonomer.Func; namespace Mixonomer.Func;
@ -17,10 +18,14 @@ namespace Mixonomer.Func;
public class RunUserPlaylist : ICloudEventFunction<MessagePublishedData> public class RunUserPlaylist : ICloudEventFunction<MessagePublishedData>
{ {
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly UserRepo _userRepo;
private readonly SpotifyNetworkProvider _spotifyMetworkProvider;
public RunUserPlaylist(ILogger<RunUserPlaylist> logger) public RunUserPlaylist(ILogger<RunUserPlaylist> logger, UserRepo userRepo, SpotifyNetworkProvider spotifyMetworkProvider)
{ {
_logger = logger; _logger = logger;
_userRepo = userRepo;
_spotifyMetworkProvider = spotifyMetworkProvider;
} }
@ -28,9 +33,10 @@ public class RunUserPlaylist : ICloudEventFunction<MessagePublishedData>
{ {
_logger.LogInformation($"Received message in C# {data.Message}, {cloudEvent.GetPopulatedAttributes()}"); _logger.LogInformation($"Received message in C# {data.Message}, {cloudEvent.GetPopulatedAttributes()}");
var userRepo = new UserRepo(projectId: System.Environment.GetEnvironmentVariable("GOOGLE_CLOUD_PROJECT")); var user = await _userRepo.GetUser(data.Message.Attributes["username"]);
var user = await userRepo.GetUser(data.Message.Attributes["username"]); var spotifyConfig = await _spotifyMetworkProvider.GetUserConfig(user);
var spotifyClient = new SpotifyClient(spotifyConfig);
_logger.LogInformation($"{user.username} was last refreshed at {user.last_refreshed}"); _logger.LogInformation($"{user.username} was last refreshed at {user.last_refreshed}");
} }
@ -43,5 +49,10 @@ public class RunUserPlaylistStartup : FunctionsStartup
base.ConfigureServices(context, services); base.ConfigureServices(context, services);
services.AddSecretManagerServiceClient(); services.AddSecretManagerServiceClient();
// services.AddFirestoreClient();
services.AddTransient<SpotifyNetworkProvider>()
.AddTransient<PlaylistGenerator>()
.AddSingleton<UserRepo>();
} }
} }

View File

@ -11,7 +11,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mixonomer.Tests", "Mixonome
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mixonomer.CLI", "Mixonomer.CLI\Mixonomer.CLI.csproj", "{7469F571-DBF2-4D60-85FE-4041C445A490}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mixonomer.CLI", "Mixonomer.CLI\Mixonomer.CLI.csproj", "{7469F571-DBF2-4D60-85FE-4041C445A490}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mixonomer.Playlist", "Mixonomer.Playlist\Mixonomer.Playlist.csproj", "{274560D0-2EBB-4C6D-BA45-2270DB92F12C}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mixonomer", "Mixonomer\Mixonomer.csproj", "{274560D0-2EBB-4C6D-BA45-2270DB92F12C}"
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution

View File

@ -12,7 +12,12 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Inflatable.Lastfm" Version="1.2.0" />
<PackageReference Include="SpotifyAPI.Web" Version="7.0.2" /> <PackageReference Include="SpotifyAPI.Web" Version="7.0.2" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="Last.fm\" />
</ItemGroup>
</Project> </Project>

View File

@ -1,4 +1,4 @@
namespace Mixonomer.Playlist; namespace Mixonomer;
public static class Months public static class Months
{ {

View File

@ -2,7 +2,7 @@ using Google.Cloud.Firestore;
using Mixonomer.Fire; using Mixonomer.Fire;
using Mixonomer.Fire.Extensions; using Mixonomer.Fire.Extensions;
namespace Mixonomer.Playlist; namespace Mixonomer;
public class PartTreeWalker public class PartTreeWalker
{ {
@ -10,22 +10,24 @@ public class PartTreeWalker
private readonly HashSet<string> _processedPlaylists = new(); private readonly HashSet<string> _processedPlaylists = new();
public HashSet<string>? SpotifyPlaylistNames { get; private set; } public HashSet<string>? SpotifyPlaylistNames { get; private set; }
private List<Playlist> _userPlaylists;
public PartTreeWalker(UserRepo userRepo) public PartTreeWalker(UserRepo userRepo)
{ {
_userRepo = userRepo; _userRepo = userRepo;
} }
public async Task<IEnumerable<string>> GetPlaylistParts(string username, string playlistName) public async Task<IEnumerable<string>?> GetPlaylistParts(string username, string playlistName)
{ {
var user = await _userRepo.GetUser(username); var user = await _userRepo.GetUser(username);
return await GetPlaylistParts(user, playlistName); return await GetPlaylistParts(user, playlistName);
} }
public async Task<IEnumerable<string>> GetPlaylistParts(User user, string playlistName) public async Task<IEnumerable<string>?> GetPlaylistParts(User user, string playlistName)
{ {
var playlist = await _userRepo.GetPlaylists(user).Where(x => x.name == playlistName).FirstOrDefaultAsync(); _userPlaylists = await _userRepo.GetPlaylists(user).ToListAsync();
var playlist = _userPlaylists.SingleOrDefault(x => x.name == playlistName);
if (playlist is not null) if (playlist is not null)
{ {
@ -33,20 +35,22 @@ public class PartTreeWalker
foreach (var part in playlist.playlist_references) foreach (var part in playlist.playlist_references)
{ {
await ProcessPlaylist(part); ProcessPlaylist(part);
} }
} }
return SpotifyPlaylistNames; return SpotifyPlaylistNames;
} }
private async Task ProcessPlaylist(DocumentReference documentReference) private void ProcessPlaylist(DocumentReference documentReference)
{ {
if (!_processedPlaylists.Contains(documentReference.Id)) if (!_processedPlaylists.Contains(documentReference.Id))
{ {
var playlist = (await documentReference.GetSnapshotAsync()).ConvertTo<Fire.Playlist>(); var playlist = _userPlaylists.SingleOrDefault(x => x.Reference.Id == documentReference.Id);
_processedPlaylists.Add(documentReference.Id); _processedPlaylists.Add(documentReference.Id);
if (playlist != null)
{
foreach (var p in playlist.parts) foreach (var p in playlist.parts)
{ {
SpotifyPlaylistNames?.Add(p); SpotifyPlaylistNames?.Add(p);
@ -54,7 +58,8 @@ public class PartTreeWalker
foreach (var p in playlist.playlist_references) foreach (var p in playlist.playlist_references)
{ {
await ProcessPlaylist(p); ProcessPlaylist(p);
}
} }
} }
} }

View File

@ -0,0 +1,32 @@
using Microsoft.Extensions.Logging;
using Mixonomer.Fire;
using SpotifyAPI.Web;
namespace Mixonomer;
public class PlaylistGenerator
{
private readonly ILogger<PlaylistGenerator> _logger;
private readonly UserRepo _userRepo;
private readonly SpotifyNetworkProvider _spotifyMetworkProvider;
private readonly PartTreeWalker _partTreeWalker;
public PlaylistGenerator(UserRepo userRepo, SpotifyNetworkProvider spotifyMetworkProvider, PartTreeWalker partTreeWalker, ILogger<PlaylistGenerator> logger)
{
_userRepo = userRepo;
_spotifyMetworkProvider = spotifyMetworkProvider;
_logger = logger;
_partTreeWalker = partTreeWalker;
}
public async Task GeneratePlaylist(string playlistName, string username)
{
var user = await _userRepo.GetUser(username);
var spotifyConfig = await _spotifyMetworkProvider.GetUserConfig(user);
var spotifyClient = new SpotifyClient(spotifyConfig);
var userPlaylists = await spotifyClient.Playlists.CurrentUsers();
var allPlaylists = await spotifyClient.PaginateAll(userPlaylists);
}
}

View File

@ -1,4 +1,4 @@
namespace Mixonomer.Playlist; namespace Mixonomer;
public static class SecretStrings public static class SecretStrings
{ {

View File

@ -1,17 +1,21 @@
using Google.Cloud.Firestore;
using Google.Cloud.SecretManager.V1; using Google.Cloud.SecretManager.V1;
using Microsoft.Extensions.Logging;
using Mixonomer.Fire; using Mixonomer.Fire;
using SpotifyAPI.Web; using SpotifyAPI.Web;
namespace Mixonomer.Playlist; namespace Mixonomer;
public class SpotifyNetworkProvider public class SpotifyNetworkProvider
{ {
private readonly SecretManagerServiceClient _secretClient; private readonly SecretManagerServiceClient _secretClient;
private readonly UserRepo _userRepo; private readonly UserRepo _userRepo;
private readonly ILogger<SpotifyNetworkProvider> _logger;
public SpotifyNetworkProvider(UserRepo userRepo, SecretManagerServiceClient secretClient) public SpotifyNetworkProvider(UserRepo userRepo, SecretManagerServiceClient? secretClient, ILogger<SpotifyNetworkProvider> logger)
{ {
_userRepo = userRepo; _userRepo = userRepo;
_logger = logger;
_secretClient = secretClient ?? SecretManagerServiceClient.Create(); _secretClient = secretClient ?? SecretManagerServiceClient.Create();
} }
@ -25,8 +29,8 @@ public class SpotifyNetworkProvider
var spotifyClient = await _secretClient.AccessSecretVersionAsync(SecretStrings.SPOT_CLIENT_URI); var spotifyClient = await _secretClient.AccessSecretVersionAsync(SecretStrings.SPOT_CLIENT_URI);
var spotifySecret = await _secretClient.AccessSecretVersionAsync(SecretStrings.SPOT_SECRET_URI); var spotifySecret = await _secretClient.AccessSecretVersionAsync(SecretStrings.SPOT_SECRET_URI);
var spotifyClientStr = spotifyClient.Payload.Data.ToString() ?? throw new ArgumentException("No Spotify Client ID returned"); var spotifyClientStr = spotifyClient.Payload.Data.ToStringUtf8() ?? throw new ArgumentException("No Spotify Client ID returned");
var spotifySecretStr = spotifySecret.Payload.Data.ToString() ?? throw new ArgumentException("No Spotify Secret ID returned"); var spotifySecretStr = spotifySecret.Payload.Data.ToStringUtf8() ?? throw new ArgumentException("No Spotify Secret returned");
var refreshed = await new OAuthClient() var refreshed = await new OAuthClient()
.RequestToken(new AuthorizationCodeRefreshRequest(spotifyClientStr, spotifySecretStr, user.refresh_token)); .RequestToken(new AuthorizationCodeRefreshRequest(spotifyClientStr, spotifySecretStr, user.refresh_token));
@ -41,9 +45,23 @@ public class SpotifyNetworkProvider
CreatedAt = refreshed.CreatedAt CreatedAt = refreshed.CreatedAt
}); });
authenticator.TokenRefreshed += (sender, resp) => authenticator.TokenRefreshed += async (sender, resp) =>
{ {
try
{
_logger.LogInformation("Token refreshed for [{}], writing to database", user.username);
await user.Reference.SetAsync(new
{
access_token = resp.AccessToken,
refresh_token = resp.RefreshToken,
last_refreshed = resp.CreatedAt,
token_expiry = resp.ExpiresIn
}, SetOptions.MergeAll);
}
catch (Exception e)
{
_logger.LogError(e, "Failed to write updated Spotify tokens to database for [{}]", user.username);
}
}; };
var config = SpotifyClientConfig var config = SpotifyClientConfig