feat: song likes and initial implementation of Spotify playlist support, refs #9

This commit is contained in:
2025-07-20 03:18:22 +02:00
parent 8b91a13095
commit 2e876ad628
25 changed files with 2567 additions and 56 deletions

View File

@@ -12,6 +12,7 @@
<th>Song</th>
<th>Submitter</th>
<th>Details</th>
<th></th>
</tr>
@foreach(var songSuggestion in Model.SongSuggestions)
{
@@ -26,6 +27,17 @@
<td><a href="@songSuggestion?.Song.Url" target="_blank">@string.Format("{0} - {1}", songSuggestion?.Song.Name, songSuggestion?.Song.Artist)</a></td>
<td>@displayName</td>
<td><a href="/SongSubmission/@songSuggestion?.Id">View</a></td>
<td class="=viewLike">
@if(songSuggestion?.Song != null)
{
var handler = Model.HasUserLikedThisSong(@songSuggestion.Song) ? "UnlikeSong" : "LikeSong";
var likebuttonText = Model.HasUserLikedThisSong(@songSuggestion.Song) ? "Unlike" : "Like";
<form method="post">
<input name="songId" value="@songSuggestion.Song.SongId" type="hidden" />
<input type="submit" id="songlike" value="@likebuttonText" asp-page-handler="@handler" />
</form>
}
</td>
</tr>
}
}

View File

@@ -17,14 +17,67 @@ public class IndexModel : PageModel
[BindProperty]
public List<SongSuggestion> SongSuggestions { get; set; } = new List<SongSuggestion>();
public Task OnGet()
{
private User _currentUser;
public User CurrentUser
{
get {
if(_currentUser == null)
{
var userName = this.User.Identity.Name;
using(var dci = DataContext.Instance)
{
_currentUser = dci.Users.Include(u => u.LikedSongs).Where(u => u.LdapUserName == userName).SingleOrDefault();
}
}
return _currentUser;
}
}
public bool HasUserLikedThisSong(Song song)
{
return CurrentUser.LikedSongs.Where(s => s.SongId == song.SongId).FirstOrDefault() != default(Song);
}
public Task OnGet()
{
using var dci = DataContext.Instance;
SongSuggestions = dci.SongSuggestions.OrderByDescending(s => s.Date)
.Take(50)
.Include(s => s.Song)
.Include(s => s.User)
.ToList();
return Task.CompletedTask;
}
.ToList();
return Task.CompletedTask;
}
public async Task<IActionResult> OnPostLikeSong(int? songId)
{
using (var dci = DataContext.Instance)
{
var user = dci.Users.Include(u => u.LikedSongs).Where(u => u.UserId == CurrentUser.UserId).SingleOrDefault();
if (user.LikedSongs.Find(s => s.SongId == songId) == default(Song))
{
user.LikedSongs.Add(dci.Songs.Find(songId));
await dci.SaveChangesAsync();
}
}
return RedirectToPage("/");
}
public async Task<IActionResult> OnPostUnlikeSong(int? songId)
{
using(var dci = DataContext.Instance)
{
var user = dci.Users.Include(u => u.LikedSongs).Where(u => u.UserId == CurrentUser.UserId).SingleOrDefault();
var songToRemove = user.LikedSongs.Where(s => s.SongId == songId).SingleOrDefault();
if (songToRemove != default(Song))
{
user.LikedSongs.Remove(songToRemove);
await dci.SaveChangesAsync();
}
}
return RedirectToPage("/");
}
}

View File

@@ -23,9 +23,15 @@
}
else
{
var userName = User.Identity.Name;
var dci = DataContext.Instance;
var selectedUsers = dci.Users.Where(u => u.LdapUserName == userName);
var userId = selectedUsers.SingleOrDefault()?.UserId;
dci.Dispose();
<form method="post" action="Auth/Logout">
<div>
Welcome, @User.Identity.Name!
Welcome, <a href="/User/@userId">@User.Identity.Name</a>!
</div>
<div>
<input name="submit" type="submit" value="Logout" />

View File

@@ -14,6 +14,7 @@
<th>Suggestion</th>
<th>Song</th>
<th></th>
<th></th>
</tr>
@foreach(var submission in Model.UserSongSubmissions)
{
@@ -44,6 +45,17 @@
}
<button class=@buttonClass onclick="redirectTo(@submission.Id)">@buttonText</button>
</td>
<td class="=viewLike">
@if(submission.Song != null)
{
var handler = Model.HasUserLikedThisSong(submission.Song) ? "UnlikeSong" : "LikeSong";
var likebuttonText = Model.HasUserLikedThisSong(submission.Song) ? "Unlike" : "Like";
<form method="post">
<input name="songId" value="@submission.Song.SongId" type="hidden" />
<input type="submit" id="songlike" value="@likebuttonText" asp-page-handler="@handler" />
</form>
}
</td>
</tr>
}
</table>
@@ -53,8 +65,8 @@ else
{
<div class="text-left">
<div class="suggestionHelper">
<div class="title">Today's suggestionHelper is: <b>@Model.SuggestionHelper.Title</b></div>
<div class="description" style="font-style: italic;">@Model.SuggestionHelper.Description</div>
<div class="title">Today's suggestionHelper is: <b>@Model.SuggestionHelper?.Title</b></div>
<div class="description" style="font-style: italic;">@Model.SuggestionHelper?.Description</div>
</div>
<form method="post">
<label asp-for="SubmitUrl" >Song Url:</label>

View File

@@ -75,6 +75,24 @@ public class SongSubmissionModel : PageModel
}
}
private User _currentUser;
public User CurrentUser
{
get {
if(_currentUser == null)
{
var userName = this.User.Identity.Name;
using(var dci = DataContext.Instance)
{
_currentUser = dci.Users.Include(u => u.LikedSongs).Where(u => u.LdapUserName == userName).SingleOrDefault();
}
}
return _currentUser;
}
}
public List<SpotifyAPI.Web.FullTrack> SpotifySuggestions
{
@@ -115,6 +133,11 @@ public class SongSubmissionModel : PageModel
[BindProperty(SupportsGet = true)]
public bool HasUsedSuggestion { get; set; } = true;
public bool HasUserLikedThisSong(Song song)
{
return CurrentUser.LikedSongs.Where(s => s.SongId == song.SongId).FirstOrDefault() != default(Song);
}
private async Task UpdateGroup(SongSuggestion suggestion)
{
var isItToday = suggestion.Date.Date == DateTime.Today;
@@ -223,6 +246,35 @@ public class SongSubmissionModel : PageModel
}
}
public async Task<IActionResult> OnPostLikeSong(int? songId)
{
using (var dci = DataContext.Instance)
{
var user = dci.Users.Include(u => u.LikedSongs).Where(u => u.UserId == CurrentUser.UserId).SingleOrDefault();
if (user.LikedSongs.Find(s => s.SongId == songId) == default(Song))
{
user.LikedSongs.Add(dci.Songs.Find(songId));
await dci.SaveChangesAsync();
}
}
return RedirectToPage("SongSubmission");
}
public async Task<IActionResult> OnPostUnlikeSong(int? songId)
{
using(var dci = DataContext.Instance)
{
var user = dci.Users.Include(u => u.LikedSongs).Where(u => u.UserId == CurrentUser.UserId).SingleOrDefault();
var songToRemove = user.LikedSongs.Where(s => s.SongId == songId).SingleOrDefault();
if (songToRemove != default(Song))
{
user.LikedSongs.Remove(songToRemove);
await dci.SaveChangesAsync();
}
}
return RedirectToPage("SongSubmission");
}
public IActionResult OnGetUpdate()
{
var songUrl = Request.Query["SubmitUrl"];

View File

@@ -1,4 +1,5 @@
@page "{userIndex}"
@model UserModel
@{
ViewData["Title"] = "User #" + @Model.userId;
@@ -12,6 +13,20 @@
<label asp-for="UserName">Contact Name</label>
<input asp-for="UserName" disabled />
<br />
<input type="submit" title="Submit" />
<input type="submit" value="Submit" />
</form>
@if(@Model.IsSpotifyAuthenticated)
{
<form method="post" >
<input name="userIndex" value="@Model.userId" type="hidden" />
<input type="submit" value="Deauthorize Spotify" asp-page-handler="SpotifyLogout" />
</form>
}
else
{
<form method="post" >
<input name="userIndex" value="@Model.userId" type="hidden" />
<input type="submit" value="Connect to Spotify" asp-page-handler="SpotifyLogin" />
</form>
}
</div>

View File

@@ -1,5 +1,8 @@
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using SpotifyAPI.Web;
using System.Web;
namespace sotd.Pages;
@@ -18,10 +21,12 @@ public class UserModel : PageModel
public string UserName { get; set; }
public bool IsSpotifyAuthenticated { get; set; }
[BindProperty]
public string UserNickName { get; set; }
public void OnGet(int userIndex)
public async Task OnGet(int userIndex, [FromServices] SpotifyApiClient spotifyClient)
{
using (var dci = DataContext.Instance)
{
@@ -29,10 +34,11 @@ public class UserModel : PageModel
this.UserName = user == null ? string.Empty : (user.Name ?? string.Empty);
this.UserNickName = user == null ? string.Empty : (user.NickName ?? string.Empty);
this.userId = userIndex;
this.IsSpotifyAuthenticated = await spotifyClient.IsUserAuthenticatedAsync(user);
}
}
public void OnPost(int userIndex)
public async Task OnPost(int userIndex, [FromServices] SpotifyApiClient spotifyClient)
{
using (var dci = DataContext.Instance)
{
@@ -42,6 +48,39 @@ public class UserModel : PageModel
user.NickName = this.UserNickName;
dci.SaveChanges();
this.UserName = user.Name ?? string.Empty;
this.IsSpotifyAuthenticated = await spotifyClient.IsUserAuthenticatedAsync(user);
}
}
}
public IActionResult OnPostSpotifyLogin(int userIndex, string currentUri, [FromServices] SpotifyApiClient spotifyClient)
{
_logger.LogTrace($"Attempting Spotify login for user with id {userIndex}.");
var loginRequest = new LoginRequest(
new Uri(spotifyClient.GetLoginRedirectUri()),
AppConfiguration.Instance.SpotifyClientId,
LoginRequest.ResponseType.Code)
{
Scope = new[] { Scopes.PlaylistReadPrivate, Scopes.PlaylistReadCollaborative, Scopes.PlaylistModifyPrivate, Scopes.PlaylistModifyPublic, Scopes.UgcImageUpload }
};
var redirectUri = loginRequest.ToUri().ToString() + $"&finalRedirect={HttpUtility.UrlEncode($"{Request.Scheme}://{Request.Host}:{Request.Host.Port ?? 80}{Request.Path}")}";
return this.Redirect(redirectUri);
}
public async Task OnPostSpotifyLogout(int userIndex, [FromServices] SpotifyApiClient spotifyClient)
{
_logger.LogTrace($"Attempting to deauthorize Spotify for user with id {userIndex}.");
using (var dci = DataContext.Instance)
{
var user = dci.Users?.Find(userIndex);
if (user != null)
{
await spotifyClient.DeAuthorizeUserAsync(user);
user.NickName = this.UserNickName;
this.UserName = user.Name ?? string.Empty;
}
}
}