1
0
mirror of synced 2024-11-27 16:10:53 +01:00

Add api to get best data and set favorite songs

This commit is contained in:
asesidaa 2022-09-09 01:22:01 +08:00
parent 0f71da4353
commit a0af130d1d
11 changed files with 199 additions and 12 deletions

View File

@ -0,0 +1,6 @@
namespace SharedProject.Models.Responses;
public class SongBestResponse
{
public List<SongBestData> SongBestData { get; set; } = new();
}

View File

@ -0,0 +1,22 @@
using SharedProject.Enums;
namespace SharedProject.Models;
public class SongBestData
{
public uint SongId { get; set; }
public Difficulty Difficulty { get; set; }
public uint BestScore { get; set; }
public uint BestRate { get; set; }
public CrownType BestCrown { get; set; }
public ScoreRank BestScoreRank { get; set; }
public DateTime LastPlayTime { get; set; }
public bool IsFavorite { get; set; }
}

View File

@ -6,6 +6,11 @@ namespace TaikoLocalServer.Common.Utils;
public static class GZipBytesUtil
{
public static MemoryStream GenerateStreamFromString(string value)
{
return new MemoryStream(Encoding.UTF8.GetBytes(value));
}
public static byte[] GetEmptyJsonGZipBytes()
{
var outputStream = new MemoryStream(1024);

View File

@ -9,13 +9,10 @@ namespace TaikoLocalServer.Controllers.Api;
[Route("/api/[controller]")]
public class DashboardController : BaseController<DashboardController>
{
private readonly TaikoDbContext context;
private readonly ICardService cardService;
public DashboardController(TaikoDbContext context, ICardService cardService)
public DashboardController(ICardService cardService)
{
this.context = context;
this.cardService = cardService;
}

View File

@ -0,0 +1,29 @@
using TaikoLocalServer.Services.Interfaces;
namespace TaikoLocalServer.Controllers.Api;
[ApiController]
[Route("api/[controller]")]
public class FavoriteSongsController : BaseController<FavoriteSongsController>
{
private readonly IUserDatumService userDatumService;
public FavoriteSongsController(IUserDatumService userDatumService)
{
this.userDatumService = userDatumService;
}
[HttpPost]
public async Task<IActionResult> UpdateFavoriteSong(uint baid, uint songId, bool isFavorite)
{
var user = await userDatumService.GetFirstUserDatumOrNull(baid);
if (user is null)
{
return NotFound();
}
await userDatumService.UpdateFavoriteSong(baid, songId, isFavorite);
return NoContent();
}
}

View File

@ -0,0 +1,42 @@
using SharedProject.Models.Responses;
using TaikoLocalServer.Services.Interfaces;
namespace TaikoLocalServer.Controllers.Api;
[ApiController]
[Route("api/[controller]")]
public class PlayDataController : BaseController<PlayDataController>
{
private readonly IUserDatumService userDatumService;
private readonly ISongBestDatumService songBestDatumService;
public PlayDataController(IUserDatumService userDatumService, ISongBestDatumService songBestDatumService)
{
this.userDatumService = userDatumService;
this.songBestDatumService = songBestDatumService;
}
[HttpGet("{baid}")]
public async Task<ActionResult<SongBestResponse>> GetSongBestRecords(uint baid)
{
var user = await userDatumService.GetFirstUserDatumOrNull(baid);
if (user is null)
{
return NotFound();
}
var songBestRecords = await songBestDatumService.GetAllSongBestAsModel(baid);
var favoriteSongs = await userDatumService.GetFavoriteSongIds(baid);
var favoriteSet = favoriteSongs.ToHashSet();
foreach (var songBestRecord in songBestRecords.Where(songBestRecord => favoriteSet.Contains(songBestRecord.SongId)))
{
songBestRecord.IsFavorite = true;
}
return Ok(new SongBestResponse
{
SongBestData = songBestRecords
});
}
}

View File

@ -11,13 +11,10 @@ namespace TaikoLocalServer.Controllers.Api;
[Route("/api/[controller]/{baid}")]
public class UserSettingsController : BaseController<UserSettingsController>
{
private readonly TaikoDbContext context;
private readonly IUserDatumService userDatumService;
public UserSettingsController(TaikoDbContext context, IUserDatumService userDatumService)
public UserSettingsController(IUserDatumService userDatumService)
{
this.context = context;
this.userDatumService = userDatumService;
}

View File

@ -1,8 +1,12 @@
namespace TaikoLocalServer.Services.Interfaces;
using SharedProject.Models;
namespace TaikoLocalServer.Services.Interfaces;
public interface ISongBestDatumService
{
public Task<List<SongBestDatum>> GetAllSongBestData(uint baid);
public Task UpdateOrInsertSongBestDatum(SongBestDatum datum);
public Task<List<SongBestData>> GetAllSongBestAsModel(uint baid);
}

View File

@ -13,4 +13,8 @@ public interface IUserDatumService
public Task InsertUserDatum(UserDatum userDatum);
public Task UpdateUserDatum(UserDatum userDatum);
public Task<List<uint>> GetFavoriteSongIds(uint baid);
public Task UpdateFavoriteSong(uint baid, uint songId, bool isFavorite);
}

View File

@ -1,4 +1,7 @@
using TaikoLocalServer.Services.Interfaces;
using SharedProject.Models;
using Swan.Mapping;
using TaikoLocalServer.Services.Interfaces;
using Throw;
namespace TaikoLocalServer.Services;
@ -32,4 +35,23 @@ public class SongBestDatumService : ISongBestDatumService
await context.SongBestData.AddAsync(datum);
await context.SaveChangesAsync();
}
public async Task<List<SongBestData>> GetAllSongBestAsModel(uint baid)
{
var songbestDbData = await context.SongBestData.Where(datum => datum.Baid == baid).ToListAsync();
var result = songbestDbData.Select(datum => datum.CopyPropertiesToNew<SongBestData>()).ToList();
var playLogs = await context.SongPlayData.Where(datum => datum.Baid == baid).ToListAsync();
foreach (var bestData in result)
{
var lastPlayLog = playLogs.Where(datum => datum.Difficulty == bestData.Difficulty &&
datum.SongId == bestData.SongId)
.MaxBy(datum => datum.PlayTime);
lastPlayLog.ThrowIfNull("Last play log is null! Something is wrong with db!");
bestData.LastPlayTime = lastPlayLog.PlayTime;
}
return result;
}
}

View File

@ -1,4 +1,6 @@
using TaikoLocalServer.Services.Interfaces;
using System.Text.Json;
using TaikoLocalServer.Services.Interfaces;
using Throw;
namespace TaikoLocalServer.Services;
@ -6,9 +8,12 @@ public class UserDatumService : IUserDatumService
{
private readonly TaikoDbContext context;
public UserDatumService(TaikoDbContext context)
private readonly ILogger<UserDatumService> logger;
public UserDatumService(TaikoDbContext context, ILogger<UserDatumService> logger)
{
this.context = context;
this.logger = logger;
}
public async Task<UserDatum?> GetFirstUserDatumOrNull(uint baid)
@ -50,4 +55,58 @@ public class UserDatumService : IUserDatumService
context.Update(userDatum);
await context.SaveChangesAsync();
}
public async Task<List<uint>> GetFavoriteSongIds(uint baid)
{
var userDatum = await context.UserData.FindAsync(baid);
userDatum.ThrowIfNull();
using var stringStream = GZipBytesUtil.GenerateStreamFromString(userDatum.FavoriteSongsArray);
List<uint>? result;
try
{
result = await JsonSerializer.DeserializeAsync<List<uint>>(stringStream);
}
catch (JsonException e)
{
logger.LogError(e, "Parse favorite song array json failed! Is the user initialized correctly?");
result = new List<uint>();
}
result.ThrowIfNull("Song favorite array should never be null!");
return result;
}
public async Task UpdateFavoriteSong(uint baid, uint songId, bool isFavorite)
{
var userDatum = await context.UserData.FindAsync(baid);
userDatum.ThrowIfNull();
using var stringStream = GZipBytesUtil.GenerateStreamFromString(userDatum.FavoriteSongsArray);
List<uint>? favoriteSongIds;
try
{
favoriteSongIds = await JsonSerializer.DeserializeAsync<List<uint>>(stringStream);
}
catch (JsonException e)
{
logger.LogError(e, "Parse favorite song array json failed! Is the user initialized correctly?");
favoriteSongIds = new List<uint>();
}
favoriteSongIds.ThrowIfNull("Song favorite array should never be null!");
var favoriteSet = new HashSet<uint>(favoriteSongIds);
if (isFavorite)
{
favoriteSet.Add(songId);
}
else
{
favoriteSet.Remove(songId);
}
await JsonSerializer.SerializeAsync(stringStream, favoriteSet);
context.Update(userDatum);
await context.SaveChangesAsync();
}
}