Major authentication refactors
This commit is contained in:
parent
f4cbee2e29
commit
17331bcad4
@ -1,6 +0,0 @@
|
||||
namespace SharedProject.Models.Responses;
|
||||
|
||||
public class DashboardResponse
|
||||
{
|
||||
public List<User> Users { get; set; } = new();
|
||||
}
|
@ -2,18 +2,20 @@
|
||||
using System.Security.Claims;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using TaikoLocalServer.Settings;
|
||||
using OtpNet;
|
||||
using SharedProject.Models.Requests;
|
||||
using TaikoLocalServer.Filters;
|
||||
|
||||
namespace TaikoLocalServer.Controllers.Api;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class AuthController(ICredentialService credentialService, ICardService cardService,
|
||||
IUserDatumService userDatumService, IOptions<AuthSettings> settings) : BaseController<AuthController>
|
||||
public class AuthController(IAuthService authService, IUserDatumService userDatumService,
|
||||
IOptions<AuthSettings> settings) : BaseController<AuthController>
|
||||
{
|
||||
private readonly AuthSettings authSettings = settings.Value;
|
||||
|
||||
@ -79,16 +81,17 @@ public class AuthController(ICredentialService credentialService, ICardService c
|
||||
}
|
||||
|
||||
[HttpPost("Login")]
|
||||
[AllowAnonymous]
|
||||
public async Task<IActionResult> Login(LoginRequest loginRequest)
|
||||
{
|
||||
var accessCode = loginRequest.AccessCode;
|
||||
var password = loginRequest.Password;
|
||||
|
||||
var card = await cardService.GetCardByAccessCode(accessCode);
|
||||
var card = await authService.GetCardByAccessCode(accessCode);
|
||||
if (card == null)
|
||||
return Unauthorized(new { message = "Access Code Not Found" });
|
||||
|
||||
var credential = await credentialService.GetCredentialByBaid(card.Baid);
|
||||
var credential = await authService.GetCredentialByBaid(card.Baid);
|
||||
if (credential == null)
|
||||
return Unauthorized(new { message = "Credential Not Found" });
|
||||
|
||||
@ -111,8 +114,23 @@ public class AuthController(ICredentialService credentialService, ICardService c
|
||||
// Return the token with key authToken
|
||||
return Ok(new { authToken });
|
||||
}
|
||||
|
||||
[HttpPost("LoginWithToken")]
|
||||
[ServiceFilter(typeof(AuthorizeIfRequiredAttribute))]
|
||||
public IActionResult LoginWithToken()
|
||||
{
|
||||
var tokenInfo = authService.ExtractTokenInfo(HttpContext);
|
||||
if (tokenInfo == null)
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
|
||||
[HttpPost("Register")]
|
||||
[AllowAnonymous]
|
||||
public async Task<IActionResult> Register(RegisterRequest registerRequest)
|
||||
{
|
||||
var accessCode = registerRequest.AccessCode;
|
||||
@ -121,11 +139,11 @@ public class AuthController(ICredentialService credentialService, ICardService c
|
||||
var registerWithLastPlayTime = registerRequest.RegisterWithLastPlayTime;
|
||||
var inviteCode = registerRequest.InviteCode;
|
||||
|
||||
var card = await cardService.GetCardByAccessCode(accessCode);
|
||||
var card = await authService.GetCardByAccessCode(accessCode);
|
||||
if (card == null)
|
||||
return Unauthorized(new { message = "Access Code Not Found" });
|
||||
|
||||
var credential = await credentialService.GetCredentialByBaid(card.Baid);
|
||||
var credential = await authService.GetCredentialByBaid(card.Baid);
|
||||
if (credential == null)
|
||||
return Unauthorized(new { message = "Credential Not Found" });
|
||||
|
||||
@ -156,22 +174,41 @@ public class AuthController(ICredentialService credentialService, ICardService c
|
||||
var salt = CreateSalt();
|
||||
var hashedPassword = ComputeHash(password, salt);
|
||||
|
||||
var result = await credentialService.UpdatePassword(card.Baid, hashedPassword, salt);
|
||||
var result = await authService.UpdatePassword(card.Baid, hashedPassword, salt);
|
||||
return result ? Ok() : Unauthorized( new { message = "Failed to Update Password" });
|
||||
}
|
||||
|
||||
[HttpPost("ChangePassword")]
|
||||
[ServiceFilter(typeof(AuthorizeIfRequiredAttribute))]
|
||||
public async Task<IActionResult> ChangePassword(ChangePasswordRequest changePasswordRequest)
|
||||
{
|
||||
if (authSettings.LoginRequired)
|
||||
{
|
||||
var tokenInfo = authService.ExtractTokenInfo(HttpContext);
|
||||
if (tokenInfo == null)
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
if (!tokenInfo.Value.isAdmin)
|
||||
{
|
||||
var requestBaid = authService.GetCardByAccessCode(changePasswordRequest.AccessCode).Result?.Baid;
|
||||
if (requestBaid != tokenInfo.Value.baid)
|
||||
{
|
||||
return Forbid();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var accessCode = changePasswordRequest.AccessCode;
|
||||
var oldPassword = changePasswordRequest.OldPassword;
|
||||
var newPassword = changePasswordRequest.NewPassword;
|
||||
|
||||
var card = await cardService.GetCardByAccessCode(accessCode);
|
||||
var card = await authService.GetCardByAccessCode(accessCode);
|
||||
if (card == null)
|
||||
return Unauthorized(new { message = "Access Code Not Found" });
|
||||
|
||||
var credential = await credentialService.GetCredentialByBaid(card.Baid);
|
||||
var credential = await authService.GetCredentialByBaid(card.Baid);
|
||||
if (credential == null)
|
||||
return Unauthorized(new { message = "Credential Not Found" });
|
||||
|
||||
@ -187,35 +224,57 @@ public class AuthController(ICredentialService credentialService, ICardService c
|
||||
var salt = CreateSalt();
|
||||
var hashedNewPassword = ComputeHash(newPassword, salt);
|
||||
|
||||
var result = await credentialService.UpdatePassword(card.Baid, hashedNewPassword, salt);
|
||||
var result = await authService.UpdatePassword(card.Baid, hashedNewPassword, salt);
|
||||
return result ? Ok() : Unauthorized( new { message = "Failed to Update Password" });
|
||||
}
|
||||
|
||||
[HttpPost("ResetPassword")]
|
||||
[ServiceFilter(typeof(AuthorizeIfRequiredAttribute))]
|
||||
public async Task<IActionResult> ResetPassword(ResetPasswordRequest resetPasswordRequest)
|
||||
{
|
||||
if (authSettings.LoginRequired)
|
||||
{
|
||||
var tokenInfo = authService.ExtractTokenInfo(HttpContext);
|
||||
if (tokenInfo == null)
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
if (!tokenInfo.Value.isAdmin && resetPasswordRequest.Baid != tokenInfo.Value.baid)
|
||||
{
|
||||
return Forbid();
|
||||
}
|
||||
}
|
||||
|
||||
var baid = resetPasswordRequest.Baid;
|
||||
|
||||
var credential = await credentialService.GetCredentialByBaid(baid);
|
||||
var credential = await authService.GetCredentialByBaid(baid);
|
||||
if (credential == null)
|
||||
return Unauthorized(new { message = "Credential Not Found" });
|
||||
|
||||
var result = await credentialService.UpdatePassword(baid, "", "");
|
||||
var result = await authService.UpdatePassword(baid, "", "");
|
||||
return result ? Ok() : Unauthorized( new { message = "Failed to Reset Password" });
|
||||
}
|
||||
|
||||
[HttpPost("GenerateOtp")]
|
||||
[ServiceFilter(typeof(AuthorizeIfRequiredAttribute))]
|
||||
public IActionResult GenerateOtp(GenerateOtpRequest generateOtpRequest)
|
||||
{
|
||||
if (authSettings.LoginRequired)
|
||||
{
|
||||
var tokenInfo = authService.ExtractTokenInfo(HttpContext);
|
||||
if (tokenInfo == null)
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
if (!tokenInfo.Value.isAdmin)
|
||||
{
|
||||
return Forbid();
|
||||
}
|
||||
}
|
||||
|
||||
var totp = MakeTotp(generateOtpRequest.Baid);
|
||||
return Ok(new { otp = totp.ComputeTotp() });
|
||||
}
|
||||
|
||||
[HttpPost("VerifyOtp")]
|
||||
public IActionResult VerifyOtpHandler(VerifyOtpRequest verifyOtpRequest)
|
||||
{
|
||||
if (VerifyOtp(verifyOtpRequest.Otp, verifyOtpRequest.Baid))
|
||||
return Ok();
|
||||
return Unauthorized();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,42 +1,80 @@
|
||||
using SharedProject.Models.Requests;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Claims;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.Extensions.Options;
|
||||
using SharedProject.Models.Requests;
|
||||
using TaikoLocalServer.Filters;
|
||||
using TaikoLocalServer.Settings;
|
||||
|
||||
namespace TaikoLocalServer.Controllers.Api;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class CardsController : BaseController<CardsController>
|
||||
public class CardsController(IAuthService authService, IOptions<AuthSettings> settings) : BaseController<CardsController>
|
||||
{
|
||||
private readonly ICardService cardService;
|
||||
|
||||
public CardsController(ICardService cardService)
|
||||
{
|
||||
this.cardService = cardService;
|
||||
}
|
||||
private readonly AuthSettings authSettings = settings.Value;
|
||||
|
||||
[HttpDelete("{accessCode}")]
|
||||
public async Task<IActionResult> DeleteUser(string accessCode)
|
||||
[ServiceFilter(typeof(AuthorizeIfRequiredAttribute))]
|
||||
public async Task<IActionResult> DeleteAccessCode(string accessCode)
|
||||
{
|
||||
var result = await cardService.DeleteCard(accessCode);
|
||||
if (authSettings.LoginRequired)
|
||||
{
|
||||
var tokenInfo = authService.ExtractTokenInfo(HttpContext);
|
||||
if (tokenInfo == null)
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
var card = await authService.GetCardByAccessCode(accessCode);
|
||||
if (card == null)
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
if (card.Baid != tokenInfo.Value.baid && !tokenInfo.Value.isAdmin)
|
||||
{
|
||||
return Forbid();
|
||||
}
|
||||
}
|
||||
|
||||
var result = await authService.DeleteCard(accessCode);
|
||||
|
||||
return result ? NoContent() : NotFound();
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> BindAccessCode(BindAccessCodeRequest request)
|
||||
|
||||
[HttpPost("BindAccessCode")]
|
||||
[ServiceFilter(typeof(AuthorizeIfRequiredAttribute))]
|
||||
public async Task<IActionResult> BindAccessCode(BindAccessCodeRequest bindAccessCodeRequest)
|
||||
{
|
||||
var accessCode = request.AccessCode;
|
||||
var baid = request.Baid;
|
||||
var existingCard = await cardService.GetCardByAccessCode(accessCode);
|
||||
if (authSettings.LoginRequired)
|
||||
{
|
||||
var tokenInfo = authService.ExtractTokenInfo(HttpContext);
|
||||
if (tokenInfo == null)
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
if (!tokenInfo.Value.isAdmin && tokenInfo.Value.baid != bindAccessCodeRequest.Baid)
|
||||
{
|
||||
return Forbid();
|
||||
}
|
||||
}
|
||||
|
||||
var accessCode = bindAccessCodeRequest.AccessCode;
|
||||
var baid = bindAccessCodeRequest.Baid;
|
||||
var existingCard = await authService.GetCardByAccessCode(accessCode);
|
||||
if (existingCard is not null)
|
||||
{
|
||||
return BadRequest("Access code already exists");
|
||||
}
|
||||
|
||||
var newCard = new Card
|
||||
{
|
||||
Baid = baid,
|
||||
AccessCode = accessCode
|
||||
Baid = baid,
|
||||
AccessCode = accessCode
|
||||
};
|
||||
await cardService.AddCard(newCard);
|
||||
await authService.AddCard(newCard);
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,23 +1,37 @@
|
||||
using SharedProject.Models;
|
||||
using Microsoft.Extensions.Options;
|
||||
using SharedProject.Models;
|
||||
using SharedProject.Models.Responses;
|
||||
using Swan.Mapping;
|
||||
using TaikoLocalServer.Filters;
|
||||
using TaikoLocalServer.Settings;
|
||||
|
||||
namespace TaikoLocalServer.Controllers.Api;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class DanBestDataController : BaseController<DanBestDataController>
|
||||
public class DanBestDataController(IDanScoreDatumService danScoreDatumService, IAuthService authService,
|
||||
IOptions<AuthSettings> settings) : BaseController<DanBestDataController>
|
||||
{
|
||||
private readonly IDanScoreDatumService danScoreDatumService;
|
||||
|
||||
public DanBestDataController(IDanScoreDatumService danScoreDatumService)
|
||||
{
|
||||
this.danScoreDatumService = danScoreDatumService;
|
||||
}
|
||||
|
||||
private readonly AuthSettings authSettings = settings.Value;
|
||||
|
||||
[HttpGet("{baid}")]
|
||||
[ServiceFilter(typeof(AuthorizeIfRequiredAttribute))]
|
||||
public async Task<IActionResult> GetDanBestData(uint baid)
|
||||
{
|
||||
if (authSettings.LoginRequired)
|
||||
{
|
||||
var tokenInfo = authService.ExtractTokenInfo(HttpContext);
|
||||
if (tokenInfo == null)
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
if (!tokenInfo.Value.isAdmin && tokenInfo.Value.baid != baid)
|
||||
{
|
||||
return Forbid();
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: Handle gaiden in here and web ui
|
||||
var danScores = await danScoreDatumService.GetDanScoreDataList(baid, DanType.Normal);
|
||||
var danDataList = new List<DanBestData>();
|
||||
|
@ -1,26 +0,0 @@
|
||||
using SharedProject.Models.Responses;
|
||||
|
||||
namespace TaikoLocalServer.Controllers.Api;
|
||||
|
||||
[ApiController]
|
||||
[Route("/api/[controller]")]
|
||||
public class DashboardController : BaseController<DashboardController>
|
||||
{
|
||||
private readonly ICardService cardService;
|
||||
|
||||
public DashboardController(ICardService cardService)
|
||||
{
|
||||
this.cardService = cardService;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<DashboardResponse> GetDashboard()
|
||||
{
|
||||
var users = await cardService.GetUsersFromCards();
|
||||
return new DashboardResponse
|
||||
{
|
||||
Users = users
|
||||
};
|
||||
}
|
||||
|
||||
}
|
@ -1,21 +1,35 @@
|
||||
using SharedProject.Models.Requests;
|
||||
using Microsoft.Extensions.Options;
|
||||
using SharedProject.Models.Requests;
|
||||
using TaikoLocalServer.Filters;
|
||||
using TaikoLocalServer.Settings;
|
||||
|
||||
namespace TaikoLocalServer.Controllers.Api;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class FavoriteSongsController : BaseController<FavoriteSongsController>
|
||||
public class FavoriteSongsController(IUserDatumService userDatumService, IAuthService authService,
|
||||
IOptions<AuthSettings> settings) : BaseController<FavoriteSongsController>
|
||||
{
|
||||
private readonly IUserDatumService userDatumService;
|
||||
|
||||
public FavoriteSongsController(IUserDatumService userDatumService)
|
||||
{
|
||||
this.userDatumService = userDatumService;
|
||||
}
|
||||
|
||||
private readonly AuthSettings authSettings = settings.Value;
|
||||
|
||||
[HttpPost]
|
||||
[ServiceFilter(typeof(AuthorizeIfRequiredAttribute))]
|
||||
public async Task<IActionResult> UpdateFavoriteSong(SetFavoriteRequest request)
|
||||
{
|
||||
if (authSettings.LoginRequired)
|
||||
{
|
||||
var tokenInfo = authService.ExtractTokenInfo(HttpContext);
|
||||
if (tokenInfo is null)
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
if (tokenInfo.Value.baid != request.Baid && !tokenInfo.Value.isAdmin)
|
||||
{
|
||||
return Forbid();
|
||||
}
|
||||
}
|
||||
|
||||
var user = await userDatumService.GetFirstUserDatumOrNull(request.Baid);
|
||||
|
||||
if (user is null)
|
||||
@ -28,8 +42,23 @@ public class FavoriteSongsController : BaseController<FavoriteSongsController>
|
||||
}
|
||||
|
||||
[HttpGet("{baid}")]
|
||||
[ServiceFilter(typeof(AuthorizeIfRequiredAttribute))]
|
||||
public async Task<IActionResult> GetFavoriteSongs(uint baid)
|
||||
{
|
||||
if (authSettings.LoginRequired)
|
||||
{
|
||||
var tokenInfo = authService.ExtractTokenInfo(HttpContext);
|
||||
if (tokenInfo is null)
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
if (tokenInfo.Value.baid != baid && !tokenInfo.Value.isAdmin)
|
||||
{
|
||||
return Forbid();
|
||||
}
|
||||
}
|
||||
|
||||
var user = await userDatumService.GetFirstUserDatumOrNull(baid);
|
||||
|
||||
if (user is null)
|
||||
|
@ -1,75 +1,79 @@
|
||||
using Riok.Mapperly.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Riok.Mapperly.Abstractions;
|
||||
using SharedProject.Models.Responses;
|
||||
using SharedProject.Models;
|
||||
using GameDatabase.Entities;
|
||||
using TaikoLocalServer.Filters;
|
||||
using TaikoLocalServer.Settings;
|
||||
|
||||
namespace TaikoLocalServer.Controllers.Api
|
||||
namespace TaikoLocalServer.Controllers.Api;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class PlayDataController(IUserDatumService userDatumService, ISongBestDatumService songBestDatumService,
|
||||
ISongPlayDatumService songPlayDatumService, IAuthService authService, IOptions<AuthSettings> settings)
|
||||
: BaseController<PlayDataController>
|
||||
{
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class PlayDataController : BaseController<PlayDataController>
|
||||
private readonly AuthSettings authSettings = settings.Value;
|
||||
|
||||
[HttpGet("{baid}")]
|
||||
[ServiceFilter(typeof(AuthorizeIfRequiredAttribute))]
|
||||
public async Task<ActionResult<SongBestResponse>> GetSongBestRecords(uint baid)
|
||||
{
|
||||
private readonly IUserDatumService userDatumService;
|
||||
private readonly ISongBestDatumService songBestDatumService;
|
||||
private readonly ISongPlayDatumService songPlayDatumService;
|
||||
private readonly SongBestResponseMapper _songBestResponseMapper; // Inject SongBestResponseMapper
|
||||
|
||||
public PlayDataController(IUserDatumService userDatumService, ISongBestDatumService songBestDatumService,
|
||||
ISongPlayDatumService songPlayDatumService, SongBestResponseMapper songBestResponseMapper)
|
||||
{
|
||||
this.userDatumService = userDatumService;
|
||||
this.songBestDatumService = songBestDatumService;
|
||||
this.songPlayDatumService = songPlayDatumService;
|
||||
_songBestResponseMapper = songBestResponseMapper; // Assign the injected mapper
|
||||
}
|
||||
|
||||
[HttpGet("{baid}")]
|
||||
public async Task<ActionResult<SongBestResponse>> GetSongBestRecords(uint baid)
|
||||
if (authSettings.LoginRequired)
|
||||
{
|
||||
var tokenInfo = authService.ExtractTokenInfo(HttpContext);
|
||||
if (tokenInfo is null)
|
||||
{
|
||||
var user = await userDatumService.GetFirstUserDatumOrNull(baid);
|
||||
if (user is null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var songBestRecords = await songBestDatumService.GetAllSongBestAsModel(baid);
|
||||
var songPlayData = await songPlayDatumService.GetSongPlayDatumByBaid(baid);
|
||||
foreach (var songBestData in songBestRecords)
|
||||
{
|
||||
var songPlayLogs = songPlayData.Where(datum => datum.SongId == songBestData.SongId &&
|
||||
datum.Difficulty == songBestData.Difficulty).ToList();
|
||||
songBestData.PlayCount = songPlayLogs.Count;
|
||||
songBestData.ClearCount = songPlayLogs.Count(datum => datum.Crown >= CrownType.Clear);
|
||||
songBestData.FullComboCount = songPlayLogs.Count(datum => datum.Crown >= CrownType.Gold);
|
||||
songBestData.PerfectCount = songPlayLogs.Count(datum => datum.Crown >= CrownType.Dondaful);
|
||||
}
|
||||
var favoriteSongs = await userDatumService.GetFavoriteSongIds(baid);
|
||||
var favoriteSet = favoriteSongs.ToHashSet();
|
||||
foreach (var songBestRecord in songBestRecords.Where(songBestRecord => favoriteSet.Contains(songBestRecord.SongId)))
|
||||
{
|
||||
songBestRecord.IsFavorite = true;
|
||||
}
|
||||
|
||||
foreach (var songBestRecord in songBestRecords)
|
||||
{
|
||||
songBestRecord.RecentPlayData = songPlayData
|
||||
.Where(datum => datum.SongId == songBestRecord.SongId && datum.Difficulty == songBestRecord.Difficulty)
|
||||
.Select(SongBestResponseMapper.MapToDto)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
return Ok(new SongBestResponse
|
||||
{
|
||||
SongBestData = songBestRecords
|
||||
});
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
if (tokenInfo.Value.baid != baid && !tokenInfo.Value.isAdmin)
|
||||
{
|
||||
return Forbid();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var user = await userDatumService.GetFirstUserDatumOrNull(baid);
|
||||
if (user is null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
[Mapper(EnumMappingStrategy = EnumMappingStrategy.ByName)]
|
||||
public partial class SongBestResponseMapper
|
||||
{
|
||||
public static partial SongPlayDatumDto MapToDto(SongPlayDatum entity);
|
||||
var songBestRecords = await songBestDatumService.GetAllSongBestAsModel(baid);
|
||||
var songPlayData = await songPlayDatumService.GetSongPlayDatumByBaid(baid);
|
||||
foreach (var songBestData in songBestRecords)
|
||||
{
|
||||
var songPlayLogs = songPlayData.Where(datum => datum.SongId == songBestData.SongId &&
|
||||
datum.Difficulty == songBestData.Difficulty).ToList();
|
||||
songBestData.PlayCount = songPlayLogs.Count;
|
||||
songBestData.ClearCount = songPlayLogs.Count(datum => datum.Crown >= CrownType.Clear);
|
||||
songBestData.FullComboCount = songPlayLogs.Count(datum => datum.Crown >= CrownType.Gold);
|
||||
songBestData.PerfectCount = songPlayLogs.Count(datum => datum.Crown >= CrownType.Dondaful);
|
||||
}
|
||||
var favoriteSongs = await userDatumService.GetFavoriteSongIds(baid);
|
||||
var favoriteSet = favoriteSongs.ToHashSet();
|
||||
foreach (var songBestRecord in songBestRecords.Where(songBestRecord => favoriteSet.Contains(songBestRecord.SongId)))
|
||||
{
|
||||
songBestRecord.IsFavorite = true;
|
||||
}
|
||||
|
||||
foreach (var songBestRecord in songBestRecords)
|
||||
{
|
||||
songBestRecord.RecentPlayData = songPlayData
|
||||
.Where(datum => datum.SongId == songBestRecord.SongId && datum.Difficulty == songBestRecord.Difficulty)
|
||||
.Select(SongBestResponseMapper.MapToDto)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
return Ok(new SongBestResponse
|
||||
{
|
||||
SongBestData = songBestRecords
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[Mapper(EnumMappingStrategy = EnumMappingStrategy.ByName)]
|
||||
public partial class SongBestResponseMapper
|
||||
{
|
||||
public static partial SongPlayDatumDto MapToDto(SongPlayDatum entity);
|
||||
}
|
@ -1,22 +1,36 @@
|
||||
using GameDatabase.Entities;
|
||||
using Microsoft.Extensions.Options;
|
||||
using SharedProject.Models;
|
||||
using SharedProject.Models.Responses;
|
||||
using TaikoLocalServer.Filters;
|
||||
using TaikoLocalServer.Settings;
|
||||
|
||||
namespace TaikoLocalServer.Controllers.Api;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class PlayHistoryController(
|
||||
IUserDatumService userDatumService,
|
||||
ISongBestDatumService songBestDatumService,
|
||||
ISongPlayDatumService songPlayDatumService)
|
||||
: BaseController<PlayDataController>
|
||||
public class PlayHistoryController(IUserDatumService userDatumService, ISongPlayDatumService songPlayDatumService,
|
||||
IAuthService authService, IOptions<AuthSettings> settings) : BaseController<PlayDataController>
|
||||
{
|
||||
private readonly ISongBestDatumService songBestDatumService = songBestDatumService;
|
||||
|
||||
private readonly AuthSettings authSettings = settings.Value;
|
||||
|
||||
[HttpGet("{baid}")]
|
||||
[ServiceFilter(typeof(AuthorizeIfRequiredAttribute))]
|
||||
public async Task<ActionResult<SongHistoryResponse>> GetSongHistory(uint baid)
|
||||
{
|
||||
if (authSettings.LoginRequired)
|
||||
{
|
||||
var tokenInfo = authService.ExtractTokenInfo(HttpContext);
|
||||
if (tokenInfo is null)
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
if (tokenInfo.Value.baid != baid && !tokenInfo.Value.isAdmin)
|
||||
{
|
||||
return Forbid();
|
||||
}
|
||||
}
|
||||
|
||||
var user = await userDatumService.GetFirstUserDatumOrNull(baid);
|
||||
if (user is null)
|
||||
{
|
||||
|
@ -1,22 +1,36 @@
|
||||
using SharedProject.Models;
|
||||
using Microsoft.Extensions.Options;
|
||||
using SharedProject.Models;
|
||||
using SharedProject.Utils;
|
||||
using TaikoLocalServer.Filters;
|
||||
using TaikoLocalServer.Settings;
|
||||
|
||||
namespace TaikoLocalServer.Controllers.Api;
|
||||
|
||||
[ApiController]
|
||||
[Route("/api/[controller]/{baid}")]
|
||||
public class UserSettingsController : BaseController<UserSettingsController>
|
||||
public class UserSettingsController(IUserDatumService userDatumService, IAuthService authService,
|
||||
IOptions<AuthSettings> settings) : BaseController<UserSettingsController>
|
||||
{
|
||||
private readonly IUserDatumService userDatumService;
|
||||
|
||||
public UserSettingsController(IUserDatumService userDatumService)
|
||||
{
|
||||
this.userDatumService = userDatumService;
|
||||
}
|
||||
|
||||
private readonly AuthSettings authSettings = settings.Value;
|
||||
|
||||
[HttpGet]
|
||||
[ServiceFilter(typeof(AuthorizeIfRequiredAttribute))]
|
||||
public async Task<ActionResult<UserSetting>> GetUserSetting(uint baid)
|
||||
{
|
||||
if (authSettings.LoginRequired)
|
||||
{
|
||||
var tokenInfo = authService.ExtractTokenInfo(HttpContext);
|
||||
if (tokenInfo is null)
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
if (tokenInfo.Value.baid != baid && !tokenInfo.Value.isAdmin)
|
||||
{
|
||||
return Forbid();
|
||||
}
|
||||
}
|
||||
|
||||
var user = await userDatumService.GetFirstUserDatumOrNull(baid);
|
||||
|
||||
if (user is null)
|
||||
@ -75,8 +89,23 @@ public class UserSettingsController : BaseController<UserSettingsController>
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ServiceFilter(typeof(AuthorizeIfRequiredAttribute))]
|
||||
public async Task<IActionResult> SaveUserSetting(uint baid, UserSetting userSetting)
|
||||
{
|
||||
if (authSettings.LoginRequired)
|
||||
{
|
||||
var tokenInfo = authService.ExtractTokenInfo(HttpContext);
|
||||
if (tokenInfo is null)
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
if (tokenInfo.Value.baid != baid && !tokenInfo.Value.isAdmin)
|
||||
{
|
||||
return Forbid();
|
||||
}
|
||||
}
|
||||
|
||||
var user = await userDatumService.GetFirstUserDatumOrNull(baid);
|
||||
|
||||
if (user is null)
|
||||
|
@ -1,19 +1,78 @@
|
||||
namespace TaikoLocalServer.Controllers.Api;
|
||||
using Microsoft.Extensions.Options;
|
||||
using SharedProject.Models;
|
||||
using TaikoLocalServer.Filters;
|
||||
using TaikoLocalServer.Settings;
|
||||
|
||||
namespace TaikoLocalServer.Controllers.Api;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class UsersController : BaseController<UsersController>
|
||||
public class UsersController(IUserDatumService userDatumService, IAuthService authService,
|
||||
IOptions<AuthSettings> settings) : BaseController<UsersController>
|
||||
{
|
||||
private readonly IUserDatumService userDatumService;
|
||||
|
||||
public UsersController(IUserDatumService userDatumService)
|
||||
private readonly AuthSettings authSettings = settings.Value;
|
||||
|
||||
[HttpGet("{baid}")]
|
||||
[ServiceFilter(typeof(AuthorizeIfRequiredAttribute))]
|
||||
public async Task<User?> GetUser(uint baid)
|
||||
{
|
||||
this.userDatumService = userDatumService;
|
||||
}
|
||||
if (authSettings.LoginRequired)
|
||||
{
|
||||
var tokenInfo = authService.ExtractTokenInfo(HttpContext);
|
||||
if (tokenInfo == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!tokenInfo.Value.isAdmin && tokenInfo.Value.baid != baid)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
var user = await authService.GetUserByBaid(baid);
|
||||
return user;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[ServiceFilter(typeof(AuthorizeIfRequiredAttribute))]
|
||||
public async Task<IEnumerable<User>> GetUsers()
|
||||
{
|
||||
if (authSettings.LoginRequired)
|
||||
{
|
||||
var tokenInfo = authService.ExtractTokenInfo(HttpContext);
|
||||
if (tokenInfo == null)
|
||||
{
|
||||
return Array.Empty<User>();
|
||||
}
|
||||
|
||||
if (!tokenInfo.Value.isAdmin)
|
||||
{
|
||||
return Array.Empty<User>();
|
||||
}
|
||||
}
|
||||
|
||||
return await authService.GetUsersFromCards();
|
||||
}
|
||||
|
||||
[HttpDelete("{baid}")]
|
||||
[ServiceFilter(typeof(AuthorizeIfRequiredAttribute))]
|
||||
public async Task<IActionResult> DeleteUser(uint baid)
|
||||
{
|
||||
if (authSettings.LoginRequired)
|
||||
{
|
||||
var tokenInfo = authService.ExtractTokenInfo(HttpContext);
|
||||
if (tokenInfo == null)
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
if (!tokenInfo.Value.isAdmin && tokenInfo.Value.baid != baid)
|
||||
{
|
||||
return Forbid();
|
||||
}
|
||||
}
|
||||
|
||||
var result = await userDatumService.DeleteUser(baid);
|
||||
|
||||
return result ? NoContent() : NotFound();
|
||||
|
39
TaikoLocalServer/Filters/AuthorizeIfRequiredAttribute.cs
Normal file
39
TaikoLocalServer/Filters/AuthorizeIfRequiredAttribute.cs
Normal file
@ -0,0 +1,39 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.Extensions.Options;
|
||||
using TaikoLocalServer.Settings;
|
||||
|
||||
namespace TaikoLocalServer.Filters
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
|
||||
public class AuthorizeIfRequiredAttribute(IOptions<AuthSettings> settings) : Attribute, IAsyncAuthorizationFilter
|
||||
{
|
||||
private readonly bool loginRequired = settings.Value.LoginRequired;
|
||||
|
||||
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
|
||||
{
|
||||
if (!loginRequired)
|
||||
{
|
||||
return; // Skip authorization if login is not required
|
||||
}
|
||||
|
||||
var authorizationService = context.HttpContext.RequestServices.GetRequiredService<IAuthorizationService>();
|
||||
var policyProvider = context.HttpContext.RequestServices.GetRequiredService<IAuthorizationPolicyProvider>();
|
||||
var policy = await policyProvider.GetPolicyAsync(AuthorizationPolicyNames.Default);
|
||||
|
||||
if (policy != null)
|
||||
{
|
||||
var authResult = await authorizationService.AuthorizeAsync(context.HttpContext.User, policy);
|
||||
if (!authResult.Succeeded)
|
||||
{
|
||||
context.Result = new UnauthorizedResult();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class AuthorizationPolicyNames
|
||||
{
|
||||
public const string Default = "Default";
|
||||
}
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
|
||||
namespace TaikoLocalServer.Models;
|
||||
|
||||
public class MusicInfoes
|
||||
public class MusicInfos
|
||||
{
|
||||
[JsonPropertyName("items")]
|
||||
public List<MusicInfoEntry> MusicInfoEntries { get; set; } = new();
|
@ -14,6 +14,7 @@ using Throw;
|
||||
using Serilog;
|
||||
using SharedProject.Utils;
|
||||
using TaikoLocalServer.Controllers.Api;
|
||||
using TaikoLocalServer.Filters;
|
||||
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.WriteTo.Console()
|
||||
@ -43,7 +44,8 @@ try
|
||||
builder.Configuration.AddJsonFile($"{configurationsDirectory}/ServerSettings.json", optional: false, reloadOnChange: false);
|
||||
builder.Configuration.AddJsonFile($"{configurationsDirectory}/DataSettings.json", optional: true, reloadOnChange: false);
|
||||
builder.Configuration.AddJsonFile($"{configurationsDirectory}/AuthSettings.json", optional: true, reloadOnChange: false);
|
||||
|
||||
builder.Configuration.AddJsonFile("wwwroot/appsettings.json", optional: true, reloadOnChange: true); // Add appsettings.json
|
||||
|
||||
builder.Host.UseSerilog((context, configuration) =>
|
||||
{
|
||||
configuration
|
||||
@ -70,6 +72,11 @@ try
|
||||
builder.Services.Configure<ServerSettings>(builder.Configuration.GetSection(nameof(ServerSettings)));
|
||||
builder.Services.Configure<DataSettings>(builder.Configuration.GetSection(nameof(DataSettings)));
|
||||
builder.Services.Configure<AuthSettings>(builder.Configuration.GetSection(nameof(AuthSettings)));
|
||||
|
||||
// Read LoginRequired setting from appsettings.json
|
||||
var loginRequired = builder.Configuration.GetValue<bool>("LoginRequired");
|
||||
builder.Services.Configure<AuthSettings>(options => { options.LoginRequired = loginRequired; });
|
||||
|
||||
// Add Authentication with JWT
|
||||
builder.Services.AddAuthentication(options =>
|
||||
{
|
||||
@ -89,6 +96,8 @@ try
|
||||
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration.GetSection(nameof(AuthSettings))["JwtKey"] ?? throw new InvalidOperationException()))
|
||||
};
|
||||
});
|
||||
|
||||
builder.Services.AddScoped<AuthorizeIfRequiredAttribute>(); // Register the custom attribute
|
||||
|
||||
builder.Services.AddControllers().AddProtoBufNet();
|
||||
builder.Services.AddDbContext<TaikoDbContext>(option =>
|
||||
@ -151,6 +160,10 @@ try
|
||||
app.UseStaticFiles();
|
||||
app.UseRouting();
|
||||
|
||||
// Enable Authentication and Authorization middleware
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
|
||||
app.UseHttpLogging();
|
||||
app.Use(async (context, next) =>
|
||||
{
|
||||
@ -160,6 +173,7 @@ try
|
||||
{
|
||||
Log.Error("Unknown request from: {RemoteIpAddress} {Method} {Path} {StatusCode}",
|
||||
context.Connection.RemoteIpAddress, context.Request.Method, context.Request.Path, context.Response.StatusCode);
|
||||
Log.Error("Request headers: {Headers}", context.Request.Headers);
|
||||
}
|
||||
});
|
||||
app.MapControllers();
|
||||
@ -179,4 +193,4 @@ finally
|
||||
{
|
||||
Log.Information("Shut down complete");
|
||||
Log.CloseAndFlush();
|
||||
}
|
||||
}
|
||||
|
139
TaikoLocalServer/Services/AuthService.cs
Normal file
139
TaikoLocalServer/Services/AuthService.cs
Normal file
@ -0,0 +1,139 @@
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Claims;
|
||||
using GameDatabase.Context;
|
||||
using SharedProject.Models;
|
||||
using Swan.Mapping;
|
||||
|
||||
namespace TaikoLocalServer.Services;
|
||||
|
||||
public class AuthService(TaikoDbContext context) : IAuthService
|
||||
{
|
||||
public async Task<Card?> GetCardByAccessCode(string accessCode)
|
||||
{
|
||||
return await context.Cards.FindAsync(accessCode);
|
||||
}
|
||||
|
||||
public async Task<User?> GetUserByBaid(uint baid)
|
||||
{
|
||||
var userDatum = await context.UserData.FindAsync(baid);
|
||||
if (userDatum == null) return null;
|
||||
var cardEntries = await context.Cards.Where(card => card.Baid == baid).ToListAsync();
|
||||
return new User
|
||||
{
|
||||
Baid = userDatum.Baid,
|
||||
AccessCodes = cardEntries.Select(card => card.AccessCode).ToList(),
|
||||
IsAdmin = userDatum.IsAdmin
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<List<User>> GetUsersFromCards()
|
||||
{
|
||||
var cardEntries = await context.Cards.ToListAsync();
|
||||
var userEntries = await context.UserData.ToListAsync();
|
||||
var users = userEntries.Select(userEntry => new User
|
||||
{
|
||||
Baid = userEntry.Baid,
|
||||
AccessCodes = cardEntries.Where(cardEntry => cardEntry.Baid == userEntry.Baid).Select(cardEntry => cardEntry.AccessCode).ToList(),
|
||||
IsAdmin = userEntry.IsAdmin
|
||||
}).ToList();
|
||||
return users;
|
||||
}
|
||||
|
||||
public async Task AddCard(Card card)
|
||||
{
|
||||
context.Add(card);
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteCard(string accessCode)
|
||||
{
|
||||
var card = await context.Cards.FindAsync(accessCode);
|
||||
if (card == null) return false;
|
||||
context.Cards.Remove(card);
|
||||
await context.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<List<UserCredential>> GetUserCredentialsFromCredentials()
|
||||
{
|
||||
return await context.Credentials.Select(credential => credential.CopyPropertiesToNew<UserCredential>(null)).ToListAsync();
|
||||
}
|
||||
|
||||
public async Task AddCredential(Credential credential)
|
||||
{
|
||||
context.Add(credential);
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteCredential(uint baid)
|
||||
{
|
||||
var credential = await context.Credentials.FindAsync(baid);
|
||||
|
||||
if (credential is null) return false;
|
||||
|
||||
context.Credentials.Remove(credential);
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> UpdatePassword(uint baid, string password, string salt)
|
||||
{
|
||||
var credential = await context.Credentials.FindAsync(baid);
|
||||
|
||||
if (credential is null) return false;
|
||||
|
||||
credential.Password = password;
|
||||
credential.Salt = salt;
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<Credential?> GetCredentialByBaid(uint baid)
|
||||
{
|
||||
return await context.Credentials.FindAsync(baid);
|
||||
}
|
||||
|
||||
public (uint baid, bool isAdmin)? ExtractTokenInfo(HttpContext httpContext)
|
||||
{
|
||||
var authHeader = httpContext.Request.Headers.Authorization.FirstOrDefault();
|
||||
if (authHeader == null || !authHeader.StartsWith("Bearer "))
|
||||
{
|
||||
Console.WriteLine("Invalid auth header");
|
||||
return null;
|
||||
}
|
||||
|
||||
var token = authHeader["Bearer ".Length..].Trim();
|
||||
var handler = new JwtSecurityTokenHandler();
|
||||
if (!handler.CanReadToken(token))
|
||||
{
|
||||
Console.WriteLine("Invalid token");
|
||||
return null;
|
||||
}
|
||||
|
||||
var jwtToken = handler.ReadJwtToken(token);
|
||||
if (jwtToken.ValidTo < DateTime.UtcNow)
|
||||
{
|
||||
Console.WriteLine("Token expired");
|
||||
return null;
|
||||
}
|
||||
|
||||
var claimBaid = jwtToken.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Name)?.Value;
|
||||
var claimRole = jwtToken.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Role)?.Value;
|
||||
|
||||
if (claimBaid == null || claimRole == null)
|
||||
{
|
||||
Console.WriteLine("Invalid token claims");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!uint.TryParse(claimBaid, out var baid))
|
||||
{
|
||||
Console.WriteLine("Invalid baid");
|
||||
return null;
|
||||
}
|
||||
var isAdmin = claimRole == "Admin";
|
||||
return (baid, isAdmin);
|
||||
}
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
using GameDatabase.Context;
|
||||
using SharedProject.Models;
|
||||
|
||||
namespace TaikoLocalServer.Services;
|
||||
|
||||
public class CardService : ICardService
|
||||
{
|
||||
private readonly TaikoDbContext context;
|
||||
|
||||
public CardService(TaikoDbContext context)
|
||||
{
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public async Task<Card?> GetCardByAccessCode(string accessCode)
|
||||
{
|
||||
return await context.Cards.FindAsync(accessCode);
|
||||
}
|
||||
|
||||
public async Task<List<User>> GetUsersFromCards()
|
||||
{
|
||||
var cardEntries = await context.Cards.ToListAsync();
|
||||
var userEntries = await context.UserData.ToListAsync();
|
||||
var users = userEntries.Select(userEntry => new User
|
||||
{
|
||||
Baid = (uint)userEntry.Baid,
|
||||
AccessCodes = cardEntries.Where(cardEntry => cardEntry.Baid == userEntry.Baid).Select(cardEntry => cardEntry.AccessCode).ToList(),
|
||||
IsAdmin = userEntry.IsAdmin
|
||||
}).ToList();
|
||||
return users;
|
||||
}
|
||||
|
||||
public async Task AddCard(Card card)
|
||||
{
|
||||
context.Add(card);
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteCard(string accessCode)
|
||||
{
|
||||
var card = await context.Cards.FindAsync(accessCode);
|
||||
if (card == null) return false;
|
||||
context.Cards.Remove(card);
|
||||
await context.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
using GameDatabase.Context;
|
||||
using SharedProject.Models;
|
||||
using Swan.Mapping;
|
||||
|
||||
namespace TaikoLocalServer.Services;
|
||||
|
||||
public class CredentialService(TaikoDbContext context) : ICredentialService
|
||||
{
|
||||
public async Task<List<UserCredential>> GetUserCredentialsFromCredentials()
|
||||
{
|
||||
return await context.Credentials.Select(credential => credential.CopyPropertiesToNew<UserCredential>(null)).ToListAsync();
|
||||
}
|
||||
|
||||
public async Task AddCredential(Credential credential)
|
||||
{
|
||||
context.Add(credential);
|
||||
await context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteCredential(uint baid)
|
||||
{
|
||||
var credential = await context.Credentials.FindAsync(baid);
|
||||
|
||||
if (credential is null) return false;
|
||||
|
||||
context.Credentials.Remove(credential);
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> UpdatePassword(uint baid, string password, string salt)
|
||||
{
|
||||
var credential = await context.Credentials.FindAsync(baid);
|
||||
|
||||
if (credential is null) return false;
|
||||
|
||||
credential.Password = password;
|
||||
credential.Salt = salt;
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<Credential?> GetCredentialByBaid(uint baid)
|
||||
{
|
||||
return await context.Credentials.FindAsync(baid);
|
||||
}
|
||||
}
|
@ -4,8 +4,7 @@ public static class ServiceExtensions
|
||||
{
|
||||
public static IServiceCollection AddTaikoDbServices(this IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<ICardService, CardService>();
|
||||
services.AddScoped<ICredentialService, CredentialService>();
|
||||
services.AddScoped<IAuthService, AuthService>();
|
||||
services.AddScoped<IUserDatumService, UserDatumService>();
|
||||
services.AddScoped<ISongPlayDatumService, SongPlayDatumService>();
|
||||
services.AddScoped<ISongBestDatumService, SongBestDatumService>();
|
||||
|
@ -65,11 +65,6 @@ public class GameDataService : IGameDataService
|
||||
return musicsWithUra;
|
||||
}
|
||||
|
||||
public ImmutableDictionary<uint, MusicInfoEntry> GetMusicInfoes()
|
||||
{
|
||||
return musicInfoes;
|
||||
}
|
||||
|
||||
public ImmutableDictionary<uint, MovieData> GetMovieDataDictionary()
|
||||
{
|
||||
return movieDataDictionary;
|
||||
@ -161,7 +156,7 @@ public class GameDataService : IGameDataService
|
||||
var shopFolderDataPath = Path.Combine(dataPath, settings.ShopFolderDataFileName);
|
||||
var tokenDataPath = Path.Combine(dataPath, settings.TokenDataFileName);
|
||||
var lockedSongsDataPath = Path.Combine(dataPath, settings.LockedSongsDataFileName);
|
||||
var qrCodeDataPath = Path.Combine(dataPath, settings.QRCodeDataFileName);
|
||||
var qrCodeDataPath = Path.Combine(dataPath, settings.QrCodeDataFileName);
|
||||
|
||||
var encryptedFiles = new List<string>
|
||||
{
|
||||
@ -213,7 +208,7 @@ public class GameDataService : IGameDataService
|
||||
await using var neiroFile = File.OpenRead(neiroPath);
|
||||
await using var qrCodeDataFile = File.OpenRead(qrCodeDataPath);
|
||||
|
||||
var infoesData = await JsonSerializer.DeserializeAsync<MusicInfoes>(musicInfoFile);
|
||||
var infosData = await JsonSerializer.DeserializeAsync<MusicInfos>(musicInfoFile);
|
||||
var danData = await JsonSerializer.DeserializeAsync<List<DanData>>(danDataFile);
|
||||
var gaidenData = await JsonSerializer.DeserializeAsync<List<DanData>>(gaidenDataFile);
|
||||
var introData = await JsonSerializer.DeserializeAsync<List<SongIntroductionData>>(songIntroDataFile);
|
||||
@ -227,7 +222,7 @@ public class GameDataService : IGameDataService
|
||||
var neiroData = await JsonSerializer.DeserializeAsync<Neiros>(neiroFile);
|
||||
var qrCodeData = await JsonSerializer.DeserializeAsync<List<QRCodeData>>(qrCodeDataFile);
|
||||
|
||||
InitializeMusicInfoes(infoesData);
|
||||
InitializeMusicInfos(infosData);
|
||||
|
||||
InitializeDanData(danData);
|
||||
|
||||
@ -305,11 +300,11 @@ public class GameDataService : IGameDataService
|
||||
eventFolderDictionary = eventFolderData.ToImmutableDictionary(d => d.FolderId);
|
||||
}
|
||||
|
||||
private void InitializeMusicInfoes(MusicInfoes? infoesData)
|
||||
private void InitializeMusicInfos(MusicInfos? infosData)
|
||||
{
|
||||
infoesData.ThrowIfNull("Shouldn't happen!");
|
||||
infosData.ThrowIfNull("Shouldn't happen!");
|
||||
|
||||
musicInfoes = infoesData.MusicInfoEntries.ToImmutableDictionary(info => info.MusicId);
|
||||
musicInfoes = infosData.MusicInfoEntries.ToImmutableDictionary(info => info.MusicId);
|
||||
|
||||
musics = musicInfoes.Select(pair => pair.Key)
|
||||
.ToList();
|
||||
|
@ -2,8 +2,18 @@
|
||||
|
||||
namespace TaikoLocalServer.Services.Interfaces;
|
||||
|
||||
public interface ICredentialService
|
||||
public interface IAuthService
|
||||
{
|
||||
public Task<User?> GetUserByBaid(uint baid);
|
||||
|
||||
public Task<Card?> GetCardByAccessCode(string accessCode);
|
||||
|
||||
public Task<List<User>> GetUsersFromCards();
|
||||
|
||||
public Task AddCard(Card card);
|
||||
|
||||
public Task<bool> DeleteCard(string accessCode);
|
||||
|
||||
public Task<List<UserCredential>> GetUserCredentialsFromCredentials();
|
||||
|
||||
public Task AddCredential(Credential credential);
|
||||
@ -13,4 +23,6 @@ public interface ICredentialService
|
||||
public Task<bool> UpdatePassword(uint baid, string password, string salt);
|
||||
|
||||
public Task<Credential?> GetCredentialByBaid(uint baid);
|
||||
|
||||
public (uint baid, bool isAdmin)? ExtractTokenInfo(HttpContext httpContext);
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
using SharedProject.Models;
|
||||
|
||||
namespace TaikoLocalServer.Services.Interfaces;
|
||||
|
||||
public interface ICardService
|
||||
{
|
||||
public Task<Card?> GetCardByAccessCode(string accessCode);
|
||||
|
||||
public Task<List<User>> GetUsersFromCards();
|
||||
|
||||
public Task AddCard(Card card);
|
||||
|
||||
public Task<bool> DeleteCard(string accessCode);
|
||||
}
|
@ -5,36 +5,33 @@ namespace TaikoLocalServer.Services.Interfaces;
|
||||
|
||||
public interface IGameDataService
|
||||
{
|
||||
public Task InitializeAsync();
|
||||
public Task InitializeAsync();
|
||||
|
||||
public List<uint> GetMusicList();
|
||||
public List<uint> GetMusicList();
|
||||
|
||||
public List<uint> GetMusicWithUraList();
|
||||
|
||||
public ImmutableDictionary<uint, MusicInfoEntry> GetMusicInfoes();
|
||||
public List<uint> GetMusicWithUraList();
|
||||
|
||||
public ImmutableDictionary<uint, SongIntroductionData> GetSongIntroductionDictionary();
|
||||
public ImmutableDictionary<uint, SongIntroductionData> GetSongIntroductionDictionary();
|
||||
|
||||
public ImmutableDictionary<uint, MovieData> GetMovieDataDictionary();
|
||||
public ImmutableDictionary<uint, MovieData> GetMovieDataDictionary();
|
||||
|
||||
public ImmutableDictionary<uint, EventFolderData> GetEventFolderDictionary();
|
||||
public ImmutableDictionary<uint, EventFolderData> GetEventFolderDictionary();
|
||||
|
||||
public ImmutableDictionary<uint, DanData> GetCommonDanDataDictionary();
|
||||
public ImmutableDictionary<uint, DanData> GetCommonDanDataDictionary();
|
||||
|
||||
public ImmutableDictionary<uint, DanData> GetCommonGaidenDataDictionary();
|
||||
public ImmutableDictionary<uint, DanData> GetCommonGaidenDataDictionary();
|
||||
|
||||
public List<ShopFolderData> GetShopFolderList();
|
||||
public List<ShopFolderData> GetShopFolderList();
|
||||
|
||||
public Dictionary<string, int> GetTokenDataDictionary();
|
||||
public Dictionary<string, int> GetTokenDataDictionary();
|
||||
|
||||
public List<uint> GetLockedSongsList();
|
||||
public List<uint> GetLockedSongsList();
|
||||
|
||||
public List<int> GetCostumeFlagArraySizes();
|
||||
public List<int> GetCostumeFlagArraySizes();
|
||||
|
||||
public int GetTitleFlagArraySize();
|
||||
public int GetTitleFlagArraySize();
|
||||
|
||||
public int GetToneFlagArraySize();
|
||||
public int GetToneFlagArraySize();
|
||||
|
||||
public ImmutableDictionary<string, uint> GetQRCodeDataDictionary();
|
||||
}
|
||||
|
||||
public ImmutableDictionary<string, uint> GetQRCodeDataDictionary();
|
||||
}
|
@ -3,18 +3,8 @@ using Throw;
|
||||
|
||||
namespace TaikoLocalServer.Services;
|
||||
|
||||
public class UserDatumService : IUserDatumService
|
||||
public class UserDatumService(TaikoDbContext context) : IUserDatumService
|
||||
{
|
||||
private readonly 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)
|
||||
{
|
||||
return await context.UserData
|
||||
|
@ -7,4 +7,6 @@ public class AuthSettings
|
||||
public string JwtIssuer { get; set; } = string.Empty;
|
||||
|
||||
public string JwtAudience { get; set; } = string.Empty;
|
||||
|
||||
public bool LoginRequired { get; set; }
|
||||
}
|
@ -18,5 +18,9 @@ public class DataSettings
|
||||
|
||||
public string LockedSongsDataFileName { get; set; } = "locked_songs_data.json";
|
||||
|
||||
public string QRCodeDataFileName { get; set; } = "qrcode_data.json";
|
||||
public string QrCodeDataFileName { get; set; } = "qrcode_data.json";
|
||||
|
||||
public string LockedCostumeDataFileName { get; set; } = "locked_costume_data.json";
|
||||
|
||||
public string LockedTitleDataFileName { get; set; } = "locked_title_data.json";
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
@inherits LayoutComponentBase
|
||||
@inject Blazored.LocalStorage.ILocalStorageService LocalStorage
|
||||
@inject HttpClient Client
|
||||
@inject LoginService LoginService
|
||||
@inject AuthService AuthService
|
||||
|
||||
<MudThemeProvider IsDarkMode="@isDarkMode" Theme="@taikoWebUiTheme" />
|
||||
<MudDialogProvider />
|
||||
@ -53,24 +53,10 @@
|
||||
isDarkMode = await LocalStorage.GetItemAsync<bool>("isDarkMode");
|
||||
}
|
||||
|
||||
if (LoginService.LoginRequired)
|
||||
if (AuthService.LoginRequired)
|
||||
{
|
||||
// If not logged in, attempt to use JwtToken from local storage to log in
|
||||
var hasJwtToken = await LocalStorage.ContainKeyAsync("authToken");
|
||||
if (hasJwtToken)
|
||||
{
|
||||
var authToken = await LocalStorage.GetItemAsync<string>("authToken");
|
||||
if (!string.IsNullOrWhiteSpace(authToken))
|
||||
{
|
||||
// Attempt to log in with the token
|
||||
var loginResult = await LoginService.LoginWithAuthToken(authToken, Client);
|
||||
if (!loginResult)
|
||||
{
|
||||
// Failed to log in with the token, remove it
|
||||
await LocalStorage.RemoveItemAsync("authToken");
|
||||
}
|
||||
}
|
||||
}
|
||||
await AuthService.LoginWithAuthToken();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
@inject LoginService LoginService
|
||||
@inject AuthService AuthService
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject IDialogService DialogService
|
||||
|
||||
@ -6,38 +6,38 @@
|
||||
|
||||
<MudNavMenu Rounded="true" Class="pa-2" Margin="Margin.Dense" Color="Color.Primary">
|
||||
<MudNavLink Href="/" Match="NavLinkMatch.All" Icon="@Icons.Material.Filled.Dashboard">@Localizer["Dashboard"]</MudNavLink>
|
||||
@if (LoginService.IsAdmin || !LoginService.LoginRequired)
|
||||
@if (AuthService.IsAdmin || !AuthService.LoginRequired)
|
||||
{
|
||||
<MudNavLink Href="/Users" Match="NavLinkMatch.All" Icon="@Icons.Material.Filled.People">@Localizer["Users"]</MudNavLink>
|
||||
}
|
||||
|
||||
@{
|
||||
var currentUser = LoginService.GetLoggedInUser();
|
||||
|
||||
if (LoginService.LoginRequired && !LoginService.OnlyAdmin && !LoginService.IsLoggedIn) {
|
||||
var baid = AuthService.GetLoggedInBaid();
|
||||
|
||||
if (AuthService.LoginRequired && !AuthService.OnlyAdmin && !AuthService.IsLoggedIn) {
|
||||
<MudDivider />
|
||||
<MudNavLink Href="/Login" Match="NavLinkMatch.All" Icon="@Icons.Material.Filled.Login">@Localizer["Log In"]</MudNavLink>
|
||||
<MudNavLink Href="/Register" Match="NavLinkMatch.All" Icon="@Icons.Material.Filled.AddCard">@Localizer["Register"]</MudNavLink>
|
||||
}
|
||||
|
||||
if (LoginService.IsLoggedIn && currentUser != null)
|
||||
if (AuthService.IsLoggedIn)
|
||||
{
|
||||
<MudDivider />
|
||||
<MudNavLink Href="@($"Users/{currentUser.Baid}/Profile")" Match="NavLinkMatch.All" Icon="@Icons.Material.Filled.Person">@Localizer["Profile"]</MudNavLink>
|
||||
<MudNavLink Href="@($"Users/{baid}/Profile")" Match="NavLinkMatch.All" Icon="@Icons.Material.Filled.Person">@Localizer["Profile"]</MudNavLink>
|
||||
<MudNavGroup Title=@Localizer["Play Data"] Expanded="true" Icon="@Icons.Material.Filled.EmojiEvents">
|
||||
<MudNavLink Href="@($"Users/{currentUser.Baid}/Songs")" Match="NavLinkMatch.All">@Localizer["Song List"]</MudNavLink>
|
||||
<MudNavLink Href="@($"Users/{currentUser.Baid}/HighScores")" Match="NavLinkMatch.All">@Localizer["High Scores"]</MudNavLink>
|
||||
<MudNavLink Href="@($"Users/{currentUser.Baid}/PlayHistory")" Match="NavLinkMatch.All">@Localizer["Play History"]</MudNavLink>
|
||||
<MudNavLink Href="@($"Users/{currentUser.Baid}/DaniDojo")" Match="NavLinkMatch.All">@Localizer["Dani Dojo"]</MudNavLink>
|
||||
<MudNavLink Href="@($"Users/{baid}/Songs")" Match="NavLinkMatch.All">@Localizer["Song List"]</MudNavLink>
|
||||
<MudNavLink Href="@($"Users/{baid}/HighScores")" Match="NavLinkMatch.All">@Localizer["High Scores"]</MudNavLink>
|
||||
<MudNavLink Href="@($"Users/{baid}/PlayHistory")" Match="NavLinkMatch.All">@Localizer["Play History"]</MudNavLink>
|
||||
<MudNavLink Href="@($"Users/{baid}/DaniDojo")" Match="NavLinkMatch.All">@Localizer["Dani Dojo"]</MudNavLink>
|
||||
</MudNavGroup>
|
||||
<MudNavGroup Title=@Localizer["Settings"] Expanded="_settingsOpen" Icon="@Icons.Material.Filled.Settings">
|
||||
<MudNavGroup Title=@Localizer["Settings"] Expanded="settingsOpen" Icon="@Icons.Material.Filled.Settings">
|
||||
<MudNavLink OnClick="ShowQrCode">@Localizer["Show QR Code"]</MudNavLink>
|
||||
<MudNavLink Href="/ChangePassword" Match="NavLinkMatch.All">@Localizer["Change Password"]</MudNavLink>
|
||||
<MudNavLink Href="@($"Users/{currentUser.Baid}/AccessCode")" Match="NavLinkMatch.All">@Localizer["Access Codes"]</MudNavLink>
|
||||
<MudNavLink Href="@($"Users/{baid}/AccessCode")" Match="NavLinkMatch.All">@Localizer["Access Codes"]</MudNavLink>
|
||||
</MudNavGroup>
|
||||
}
|
||||
|
||||
if (LoginService.IsLoggedIn)
|
||||
if (AuthService.IsLoggedIn)
|
||||
{
|
||||
<MudDivider />
|
||||
<MudNavLink Match="NavLinkMatch.All" Icon="@Icons.Material.Filled.Logout" IconColor="Color.Error" OnClick="Logout">@Localizer["Log Out"]</MudNavLink>
|
||||
@ -46,14 +46,14 @@
|
||||
</MudNavMenu>
|
||||
|
||||
@code {
|
||||
private bool _settingsOpen = false;
|
||||
private bool settingsOpen = false;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
LoginService.LoginStatusChanged += HandleLoginStatusChanged;
|
||||
AuthService.LoginStatusChanged += HandleAuthStatusChanged;
|
||||
}
|
||||
|
||||
private void HandleLoginStatusChanged(object? sender, EventArgs e)
|
||||
private void HandleAuthStatusChanged(object? sender, EventArgs e)
|
||||
{
|
||||
StateHasChanged();
|
||||
}
|
||||
@ -62,22 +62,25 @@
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
LoginService.LoginStatusChanged += HandleLoginStatusChanged;
|
||||
AuthService.LoginStatusChanged += HandleAuthStatusChanged;
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowQrCode()
|
||||
private async Task ShowQrCode()
|
||||
{
|
||||
var user = await AuthService.GetLoggedInUser();
|
||||
if (user == null) return;
|
||||
|
||||
var parameters = new DialogParameters
|
||||
{
|
||||
["user"] = LoginService.GetLoggedInUser()
|
||||
["user"] = user
|
||||
};
|
||||
|
||||
var options = new DialogOptions() { DisableBackdropClick = true };
|
||||
DialogService.Show<UserQrCodeDialog>(@Localizer["QR Code"], parameters, options);
|
||||
var options = new DialogOptions { DisableBackdropClick = true };
|
||||
await DialogService.ShowAsync<UserQrCodeDialog>(Localizer["QR Code"], parameters, options);
|
||||
|
||||
// Prevent the settings menu from closing
|
||||
_settingsOpen = true;
|
||||
settingsOpen = true;
|
||||
}
|
||||
|
||||
private async Task Logout()
|
||||
@ -90,7 +93,7 @@
|
||||
|
||||
if (result == true)
|
||||
{
|
||||
LoginService.Logout();
|
||||
await AuthService.Logout();
|
||||
NavigationManager.NavigateTo("/");
|
||||
}
|
||||
}
|
||||
|
@ -17,52 +17,52 @@
|
||||
<MudTable Items="Items" Elevation="0" Striped="true">
|
||||
<HeaderContent>
|
||||
<MudTh>
|
||||
<MudTableSortLabel InitialDirection="SortDirection.Descending" T="SongPlayDatumDto" SortBy="x => x.PlayTime">
|
||||
<MudTableSortLabel InitialDirection="SortDirection.Descending" T="SongHistoryData" SortBy="x => x.PlayTime">
|
||||
@Localizer["Play Time"]
|
||||
</MudTableSortLabel>
|
||||
</MudTh>
|
||||
<MudTh>
|
||||
<MudTableSortLabel T="SongPlayDatumDto" SortBy="x => x.Difficulty">
|
||||
<MudTableSortLabel T="SongHistoryData" SortBy="x => x.Difficulty">
|
||||
@Localizer["Difficulty"]
|
||||
</MudTableSortLabel>
|
||||
</MudTh>
|
||||
<MudTh>
|
||||
<MudTableSortLabel T="SongPlayDatumDto" SortBy="x => x.Crown">
|
||||
<MudTableSortLabel T="SongHistoryData" SortBy="x => x.Crown">
|
||||
@Localizer["Crown"]
|
||||
</MudTableSortLabel>
|
||||
</MudTh>
|
||||
<MudTh>
|
||||
<MudTableSortLabel T="SongPlayDatumDto" SortBy="x => x.ScoreRank">
|
||||
<MudTableSortLabel T="SongHistoryData" SortBy="x => x.ScoreRank">
|
||||
@Localizer["Rank"]
|
||||
</MudTableSortLabel>
|
||||
</MudTh>
|
||||
<MudTh>
|
||||
<MudTableSortLabel T="SongPlayDatumDto" SortBy="x => x.Score">
|
||||
<MudTableSortLabel T="SongHistoryData" SortBy="x => x.Score">
|
||||
@Localizer["Score"]
|
||||
</MudTableSortLabel>
|
||||
</MudTh>
|
||||
<MudTh>
|
||||
<MudTableSortLabel T="SongPlayDatumDto" SortBy="x => x.GoodCount">
|
||||
<MudTableSortLabel T="SongHistoryData" SortBy="x => x.GoodCount">
|
||||
@Localizer["Good"]
|
||||
</MudTableSortLabel>
|
||||
</MudTh>
|
||||
<MudTh>
|
||||
<MudTableSortLabel T="SongPlayDatumDto" SortBy="x => x.OkCount">
|
||||
<MudTableSortLabel T="SongHistoryData" SortBy="x => x.OkCount">
|
||||
@Localizer["OK"]
|
||||
</MudTableSortLabel>
|
||||
</MudTh>
|
||||
<MudTh>
|
||||
<MudTableSortLabel T="SongPlayDatumDto" SortBy="x => x.MissCount">
|
||||
<MudTableSortLabel T="SongHistoryData" SortBy="x => x.MissCount">
|
||||
@Localizer["Bad"]
|
||||
</MudTableSortLabel>
|
||||
</MudTh>
|
||||
<MudTh>
|
||||
<MudTableSortLabel T="SongPlayDatumDto" SortBy="x => x.DrumrollCount">
|
||||
<MudTableSortLabel T="SongHistoryData" SortBy="x => x.DrumrollCount">
|
||||
@Localizer["Drumroll"]
|
||||
</MudTableSortLabel>
|
||||
</MudTh>
|
||||
<MudTh>
|
||||
<MudTableSortLabel T="SongPlayDatumDto" SortBy="x => x.ComboCount">
|
||||
<MudTableSortLabel T="SongHistoryData" SortBy="x => x.ComboCount">
|
||||
@Localizer["MAX Combo"]
|
||||
</MudTableSortLabel>
|
||||
</MudTh>
|
||||
@ -102,6 +102,6 @@
|
||||
</MudCard>
|
||||
|
||||
@code {
|
||||
[Parameter] public List<SongPlayDatumDto> Items { get; set; } = new();
|
||||
[Parameter] public List<SongHistoryData> Items { get; set; } = new();
|
||||
private const string IconStyle = "width:25px; height:25px;";
|
||||
}
|
||||
|
@ -1,14 +1,13 @@
|
||||
@using System.Text.Json
|
||||
@using Microsoft.AspNetCore.Components;
|
||||
@using TaikoWebUI.Pages.Dialogs;
|
||||
@inject TaikoWebUI.Utilities.StringUtil StringUtil;
|
||||
@inject Utilities.StringUtil StringUtil;
|
||||
@inject IDialogService DialogService;
|
||||
@inject LoginService LoginService;
|
||||
@inject AuthService AuthService;
|
||||
@inject HttpClient Client
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
|
||||
@if (user is not null)
|
||||
@if (User is not null)
|
||||
{
|
||||
<MudCard Outlined="true">
|
||||
<MudCardHeader>
|
||||
@ -22,30 +21,30 @@
|
||||
<MudSkeleton Width="35%" Height="32px" />
|
||||
}
|
||||
|
||||
@if (LoginService.LoginRequired && user?.IsAdmin == true)
|
||||
@if (AuthService.LoginRequired && User?.IsAdmin == true)
|
||||
{
|
||||
<MudChip Variant="Variant.Outlined" Color="Color.Info" Size="Size.Small" Icon="@Icons.Material.TwoTone.AdminPanelSettings">@Localizer["Admin"]</MudChip>
|
||||
}
|
||||
</div>
|
||||
<MudText Typo="Typo.caption">@Localizer["User"] BAID: @user?.Baid</MudText>
|
||||
<MudText Typo="Typo.caption">@Localizer["User"] BAID: @User?.Baid</MudText>
|
||||
</CardHeaderContent>
|
||||
<CardHeaderActions>
|
||||
<MudMenu Icon="@Icons.Material.Filled.MoreVert" Dense="true" AnchorOrigin="Origin.BottomLeft"
|
||||
TransformOrigin="Origin.TopLeft" Size="Size.Small">
|
||||
<MudMenuItem Icon="@Icons.Material.Filled.QrCode"
|
||||
OnClick="@(_ => ShowQrCode(user))"
|
||||
OnTouch="@(_ => ShowQrCode(user))"
|
||||
OnClick="@(_ => ShowQrCode(User))"
|
||||
OnTouch="@(_ => ShowQrCode(User))"
|
||||
IconColor="@Color.Primary">
|
||||
@Localizer["Show QR Code"]
|
||||
</MudMenuItem>
|
||||
<MudDivider />
|
||||
<MudMenuItem Icon="@Icons.Material.Filled.FeaturedPlayList"
|
||||
Href="@($"Users/{user.Baid}/AccessCode")"
|
||||
Href="@($"Users/{User.Baid}/AccessCode")"
|
||||
IconColor="@Color.Primary">
|
||||
@Localizer["Access Codes"]
|
||||
</MudMenuItem>
|
||||
<MudDivider />
|
||||
@if (LoginService.OnlyAdmin || LoginService.LoginRequired)
|
||||
@if (AuthService.OnlyAdmin || AuthService.LoginRequired)
|
||||
{
|
||||
<MudMenuItem Icon="@Icons.Material.Filled.Lock"
|
||||
Href="@($"/ChangePassword")"
|
||||
@ -54,31 +53,31 @@
|
||||
</MudMenuItem>
|
||||
<MudDivider />
|
||||
}
|
||||
@if (LoginService.LoginRequired && LoginService.IsAdmin)
|
||||
@if (AuthService.LoginRequired && AuthService.IsAdmin)
|
||||
{
|
||||
<MudMenuItem Icon="@Icons.Material.Filled.Password"
|
||||
OnClick="@(_ => GenerateInviteCode(user.Baid))"
|
||||
OnTouch="@(_ => GenerateInviteCode(user.Baid))"
|
||||
OnClick="@(_ => GenerateInviteCode(User.Baid))"
|
||||
OnTouch="@(_ => GenerateInviteCode(User.Baid))"
|
||||
IconColor="@Color.Primary">
|
||||
@Localizer["Generate Invite Code"]
|
||||
</MudMenuItem>
|
||||
<MudDivider />
|
||||
}
|
||||
@if (LoginService.LoginRequired && LoginService.IsAdmin)
|
||||
@if (AuthService.LoginRequired && AuthService.IsAdmin)
|
||||
{
|
||||
<MudMenuItem Icon="@Icons.Material.Filled.LockReset"
|
||||
OnClick="@(_ => ResetPassword(user))"
|
||||
OnTouch="@(_ => ResetPassword(user))"
|
||||
OnClick="@(_ => ResetPassword(User))"
|
||||
OnTouch="@(_ => ResetPassword(User))"
|
||||
IconColor="@Color.Primary">
|
||||
@Localizer["Unregister"]
|
||||
</MudMenuItem>
|
||||
<MudDivider />
|
||||
}
|
||||
@if (LoginService.AllowUserDelete)
|
||||
@if (AuthService.AllowUserDelete)
|
||||
{
|
||||
<MudMenuItem Icon="@Icons.Material.Filled.Delete"
|
||||
OnClick="@(_ => DeleteUser(user))"
|
||||
OnTouch="@(_ => DeleteUser(user))"
|
||||
OnClick="@(_ => DeleteUser(User))"
|
||||
OnTouch="@(_ => DeleteUser(User))"
|
||||
IconColor="@Color.Error">
|
||||
@Localizer["Delete User"]
|
||||
</MudMenuItem>
|
||||
@ -89,9 +88,9 @@
|
||||
<MudCardContent>
|
||||
<MudText Typo="Typo.body2" Style="font-weight:bold">@Localizer["Access Code"]</MudText>
|
||||
<MudText Style="font-family:monospace;overflow:hidden;overflow-x:scroll">
|
||||
@if (user.AccessCodes.Count > 0)
|
||||
@if (User.AccessCodes.Count > 0)
|
||||
{
|
||||
@foreach (var digitGroup in StringUtil.SplitIntoGroups(user.AccessCodes[0], 4))
|
||||
@foreach (var digitGroup in StringUtil.SplitIntoGroups(User.AccessCodes[0], 4))
|
||||
{
|
||||
<span class="mr-2">@digitGroup</span>
|
||||
}
|
||||
@ -100,9 +99,9 @@
|
||||
<span class="mr-2">@Localizer["N/A"]</span>
|
||||
}
|
||||
</MudText>
|
||||
@if (user.AccessCodes.Count > 1)
|
||||
@if (User.AccessCodes.Count > 1)
|
||||
{
|
||||
<MudText Typo="Typo.caption">... @Localizer["and"] @(user.AccessCodes.Count - 1) @Localizer["other access code(s)"]</MudText>
|
||||
<MudText Typo="Typo.caption">... @Localizer["and"] @(User.AccessCodes.Count - 1) @Localizer["other access code(s)"]</MudText>
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -112,7 +111,7 @@
|
||||
</MudCardContent>
|
||||
<MudCardActions>
|
||||
<MudStack Row="true" Style="width:100%" Spacing="4" Justify="Justify.FlexEnd">
|
||||
<MudButton Href="@($"Users/{user.Baid}/Profile")"
|
||||
<MudButton Href="@($"Users/{User.Baid}/Profile")"
|
||||
Size="Size.Small" Variant="Variant.Text" StartIcon="@Icons.Material.Filled.Edit"
|
||||
Color="Color.Primary">
|
||||
@Localizer["edit profile"]
|
||||
@ -126,10 +125,10 @@
|
||||
FullWidth="true"
|
||||
AnchorOrigin="Origin.BottomCenter"
|
||||
TransformOrigin="Origin.TopCenter">
|
||||
<MudMenuItem Href="@($"Users/{user.Baid}/HighScores")">@Localizer["High Scores"]</MudMenuItem>
|
||||
<MudMenuItem Href="@($"Users/{user.Baid}/PlayHistory")">@Localizer["Play History"]</MudMenuItem>
|
||||
<MudMenuItem Href="@($"Users/{user.Baid}/Songs")">@Localizer["Song List"]</MudMenuItem>
|
||||
<MudMenuItem Href="@($"Users/{user.Baid}/DaniDojo")">@Localizer["Dani Dojo"]</MudMenuItem>
|
||||
<MudMenuItem Href="@($"Users/{User.Baid}/HighScores")">@Localizer["High Scores"]</MudMenuItem>
|
||||
<MudMenuItem Href="@($"Users/{User.Baid}/PlayHistory")">@Localizer["Play History"]</MudMenuItem>
|
||||
<MudMenuItem Href="@($"Users/{User.Baid}/Songs")">@Localizer["Song List"]</MudMenuItem>
|
||||
<MudMenuItem Href="@($"Users/{User.Baid}/DaniDojo")">@Localizer["Dani Dojo"]</MudMenuItem>
|
||||
</MudMenu>
|
||||
</MudStack>
|
||||
</MudCardActions>
|
||||
@ -137,14 +136,14 @@
|
||||
}
|
||||
|
||||
@code {
|
||||
[Parameter] public User? user { get; set; }
|
||||
private DashboardResponse? response;
|
||||
[Parameter] public User? User { get; set; }
|
||||
private UserSetting? userSetting;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{ if (user is not null)
|
||||
{
|
||||
if (User is not null)
|
||||
{
|
||||
userSetting = await Client.GetFromJsonAsync<UserSetting>($"api/UserSettings/{user.Baid}");
|
||||
userSetting = await Client.GetFromJsonAsync<UserSetting>($"api/UserSettings/{User.Baid}");
|
||||
}
|
||||
}
|
||||
|
||||
@ -155,7 +154,7 @@
|
||||
["user"] = user
|
||||
};
|
||||
|
||||
var options = new DialogOptions() { DisableBackdropClick = true };
|
||||
var options = new DialogOptions { DisableBackdropClick = true };
|
||||
DialogService.Show<UserQrCodeDialog>(Localizer["QR Code"], parameters, options);
|
||||
|
||||
return Task.CompletedTask;
|
||||
@ -163,8 +162,8 @@
|
||||
|
||||
private async Task ResetPassword(User user)
|
||||
{
|
||||
var options = new DialogOptions() { DisableBackdropClick = true };
|
||||
if (LoginService.LoginRequired && !LoginService.IsAdmin)
|
||||
var options = new DialogOptions { DisableBackdropClick = true };
|
||||
if (AuthService.LoginRequired && !AuthService.IsAdmin)
|
||||
{
|
||||
await DialogService.ShowMessageBox(
|
||||
Localizer["Error"],
|
||||
@ -177,18 +176,14 @@
|
||||
["user"] = user
|
||||
};
|
||||
|
||||
var dialog = DialogService.Show<ResetPasswordConfirmDialog>(Localizer["Reset Password"], parameters, options);
|
||||
var result = await dialog.Result;
|
||||
|
||||
if (result.Canceled) return;
|
||||
|
||||
response = await Client.GetFromJsonAsync<DashboardResponse>("api/Dashboard");
|
||||
var dialog = await DialogService.ShowAsync<ResetPasswordConfirmDialog>(Localizer["Reset Password"], parameters, options);
|
||||
await dialog.Result;
|
||||
}
|
||||
|
||||
private async Task DeleteUser(User user)
|
||||
{
|
||||
var options = new DialogOptions() { DisableBackdropClick = true };
|
||||
if (!LoginService.AllowUserDelete)
|
||||
var options = new DialogOptions { DisableBackdropClick = true };
|
||||
if (!AuthService.AllowUserDelete)
|
||||
{
|
||||
await DialogService.ShowMessageBox(
|
||||
Localizer["Error"],
|
||||
@ -201,13 +196,16 @@
|
||||
["user"] = user
|
||||
};
|
||||
|
||||
var dialog = DialogService.Show<UserDeleteConfirmDialog>(Localizer["Delete User"], parameters, options);
|
||||
var dialog = await DialogService.ShowAsync<UserDeleteConfirmDialog>(Localizer["Delete User"], parameters, options);
|
||||
var result = await dialog.Result;
|
||||
|
||||
if (result.Canceled) return;
|
||||
|
||||
response = await Client.GetFromJsonAsync<DashboardResponse>("api/Dashboard");
|
||||
LoginService.Logout();
|
||||
|
||||
if (user.Baid == AuthService.GetLoggedInBaid())
|
||||
{
|
||||
await AuthService.Logout();
|
||||
}
|
||||
|
||||
NavigationManager.NavigateTo("/Users");
|
||||
}
|
||||
|
||||
@ -247,9 +245,7 @@
|
||||
["otp"] = otp
|
||||
};
|
||||
|
||||
var options = new DialogOptions() { DisableBackdropClick = true };
|
||||
DialogService.Show<OTPDialog>(Localizer["Invite Code"], parameters, options);
|
||||
|
||||
|
||||
var options = new DialogOptions { DisableBackdropClick = true };
|
||||
await DialogService.ShowAsync<OTPDialog>(Localizer["Invite Code"], parameters, options);
|
||||
}
|
||||
}
|
@ -1,94 +1,79 @@
|
||||
@page "/Users/{baid:int}/AccessCode"
|
||||
@inject HttpClient Client
|
||||
@inject IDialogService DialogService
|
||||
@inject LoginService LoginService
|
||||
@inject AuthService AuthService
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject TaikoWebUI.Utilities.StringUtil StringUtil;
|
||||
@inject Utilities.StringUtil StringUtil;
|
||||
|
||||
@if (response is not null)
|
||||
{
|
||||
@if ((LoginService.LoginRequired && (!LoginService.IsLoggedIn || (LoginService.GetLoggedInUser().Baid != Baid && !LoginService.IsAdmin))) || User is null)
|
||||
{
|
||||
if (!LoginService.IsLoggedIn)
|
||||
{
|
||||
NavigationManager.NavigateTo("/Login");
|
||||
}
|
||||
else
|
||||
{
|
||||
NavigationManager.NavigateTo("/");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudBreadcrumbs Items="breadcrumbs" Class="p-0 mb-2"></MudBreadcrumbs>
|
||||
<MudText Typo="Typo.h4">@Localizer["Access Codes"]</MudText>
|
||||
<MudGrid Class="my-4 pb-10">
|
||||
<MudItem xs="12">
|
||||
<MudCard Outlined="true" Class="mb-6">
|
||||
<MudCardContent>
|
||||
<MudGrid Spacing="3">
|
||||
<MudItem xs="12">
|
||||
<MudText Typo="Typo.h6">@Localizer["Add Access Code"]</MudText>
|
||||
<MudForm @ref="bindAccessCodeForm">
|
||||
<MudGrid Spacing="2" Class="mt-4">
|
||||
<MudItem xs="12" md="10">
|
||||
<MudTextField @bind-value="inputAccessCode" InputType="InputType.Text" T="string"
|
||||
FullWidth="true" Required="@true" RequiredError="Access Code is required" Variant="Variant.Outlined" Margin="Margin.Dense"
|
||||
Label=@Localizer["New Access Code"] />
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="2">
|
||||
<MudButton OnClick="OnBind" FullWidth="true" StartIcon="@Icons.Material.Filled.AddCard" Color="Color.Primary" Variant="Variant.Filled" Class="mt-1">Add</MudButton>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudForm>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
|
||||
<MudCard Outlined="true">
|
||||
<MudCardContent>
|
||||
<MudGrid Spacing="3" Class="pb-2">
|
||||
<MudItem xs="12">
|
||||
<MudText Typo="Typo.h6">@Localizer["Access Code"]</MudText>
|
||||
</MudItem>
|
||||
@for (var idx = 0; idx < User.AccessCodes.Count; idx++)
|
||||
{
|
||||
var accessCode = User.AccessCodes[idx];
|
||||
var localIdx = idx + 1;
|
||||
<MudItem xs="12" Class="py-0">
|
||||
<div Style="border-bottom:1px solid #eee; padding: 5px 0;">
|
||||
<MudGrid Spacing="2" Class="d-flex align-center">
|
||||
<MudItem xs="12" md="8" Class="d-flex align-center">
|
||||
<pre class="mb-0" style="font-size:16px">
|
||||
@foreach (var digitGroup in StringUtil.SplitIntoGroups(accessCode, 4))
|
||||
{
|
||||
<span class="mr-2">@digitGroup</span>
|
||||
}
|
||||
</pre>
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="4" Class="d-flex justify-end">
|
||||
<MudButton OnClick="@(_ => DeleteAccessCode(accessCode))"
|
||||
Size="Size.Small" Variant="Variant.Text" StartIcon="@Icons.Material.Filled.Delete"
|
||||
Color="Color.Error">
|
||||
@Localizer["Delete"]
|
||||
</MudButton>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</div>
|
||||
</MudItem>
|
||||
}
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@if (AuthService.LoginRequired && (!AuthService.IsLoggedIn || (AuthService.GetLoggedInBaid() != Baid && !AuthService.IsAdmin))) {
|
||||
NavigationManager.NavigateTo(!AuthService.IsLoggedIn ? "/Login" : "/");
|
||||
} else if (User is null) {
|
||||
// Loading ...
|
||||
<MudContainer Style="display:flex;margin:50px 0;align-items:center;justify-content:center;">
|
||||
<MudProgressCircular Indeterminate="true" Size="Size.Large" Color="Color.Primary" />
|
||||
</MudContainer>
|
||||
} else {
|
||||
<MudBreadcrumbs Items="breadcrumbs" Class="p-0 mb-2"></MudBreadcrumbs>
|
||||
<MudText Typo="Typo.h4">@Localizer["Access Codes"]</MudText>
|
||||
<MudGrid Class="my-4 pb-10">
|
||||
<MudItem xs="12">
|
||||
<MudCard Outlined="true" Class="mb-6">
|
||||
<MudCardContent>
|
||||
<MudGrid Spacing="3">
|
||||
<MudItem xs="12">
|
||||
<MudText Typo="Typo.h6">@Localizer["Add Access Code"]</MudText>
|
||||
<MudForm @ref="bindAccessCodeForm">
|
||||
<MudGrid Spacing="2" Class="mt-4">
|
||||
<MudItem xs="12" md="10">
|
||||
<MudTextField @bind-value="inputAccessCode" InputType="InputType.Text" T="string"
|
||||
FullWidth="true" Required="@true" RequiredError="Access Code is required" Variant="Variant.Outlined" Margin="Margin.Dense"
|
||||
Label=@Localizer["New Access Code"] />
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="2">
|
||||
<MudButton OnClick="OnBind" FullWidth="true" StartIcon="@Icons.Material.Filled.AddCard" Color="Color.Primary" Variant="Variant.Filled" Class="mt-1">@Localizer["Add"]</MudButton>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudForm>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
|
||||
<MudCard Outlined="true">
|
||||
<MudCardContent>
|
||||
<MudGrid Spacing="3" Class="pb-2">
|
||||
<MudItem xs="12">
|
||||
<MudText Typo="Typo.h6">@Localizer["Access Code"]</MudText>
|
||||
</MudItem>
|
||||
@for (var idx = 0; idx < User.AccessCodes.Count; idx++)
|
||||
{
|
||||
var accessCode = User.AccessCodes[idx];
|
||||
var localIdx = idx + 1;
|
||||
<MudItem xs="12" Class="py-0">
|
||||
<div Style="border-bottom:1px solid #eee; padding: 5px 0;">
|
||||
<MudGrid Spacing="2" Class="d-flex align-center">
|
||||
<MudItem xs="12" md="8" Class="d-flex align-center">
|
||||
<pre class="mb-0" style="font-size:16px">
|
||||
@foreach (var digitGroup in StringUtil.SplitIntoGroups(accessCode, 4))
|
||||
{
|
||||
<span class="mr-2">@digitGroup</span>
|
||||
}
|
||||
</pre>
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="4" Class="d-flex justify-end">
|
||||
<MudButton OnClick="@(_ => DeleteAccessCode(accessCode))"
|
||||
Size="Size.Small" Variant="Variant.Text" StartIcon="@Icons.Material.Filled.Delete"
|
||||
Color="Color.Error">
|
||||
@Localizer["Delete"]
|
||||
</MudButton>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</div>
|
||||
</MudItem>
|
||||
}
|
||||
</MudGrid>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
}
|
@ -10,9 +10,8 @@ public partial class AccessCode
|
||||
private string inputAccessCode = "";
|
||||
private MudForm bindAccessCodeForm = default!;
|
||||
|
||||
private User? User { get; set; } = new();
|
||||
|
||||
private DashboardResponse? response;
|
||||
private User? User { get; set; }
|
||||
|
||||
private UserSetting? userSetting;
|
||||
|
||||
private readonly List<BreadcrumbItem> breadcrumbs = new();
|
||||
@ -24,32 +23,28 @@ public partial class AccessCode
|
||||
|
||||
userSetting = await Client.GetFromJsonAsync<UserSetting>($"api/UserSettings/{Baid}");
|
||||
|
||||
if (LoginService.IsLoggedIn && !LoginService.IsAdmin)
|
||||
if (AuthService.IsLoggedIn && !AuthService.IsAdmin)
|
||||
{
|
||||
breadcrumbs.Add(new BreadcrumbItem(Localizer["Dashboard"], href: "/"));
|
||||
}
|
||||
else
|
||||
{
|
||||
breadcrumbs.Add(new BreadcrumbItem(Localizer["Users"], href: "/Users"));
|
||||
};
|
||||
}
|
||||
breadcrumbs.Add(new BreadcrumbItem($"{userSetting?.MyDonName}", href: null, disabled: true));
|
||||
breadcrumbs.Add(new BreadcrumbItem(Localizer["Access Codes"], href: $"/Users/{Baid}/AccessCode", disabled: false));
|
||||
}
|
||||
|
||||
private async Task InitializeUser()
|
||||
{
|
||||
response = await Client.GetFromJsonAsync<DashboardResponse>("api/Dashboard");
|
||||
LoginService.ResetLoggedInUser(response);
|
||||
if (LoginService.IsAdmin || !LoginService.LoginRequired)
|
||||
if (!AuthService.LoginRequired)
|
||||
{
|
||||
if (response is not null)
|
||||
{
|
||||
User = response.Users.FirstOrDefault(u => u.Baid == Baid);
|
||||
}
|
||||
var users = await Client.GetFromJsonAsync<List<User>>("api/Users");
|
||||
if (users != null) User = users.FirstOrDefault(u => u.Baid == Baid);
|
||||
}
|
||||
else if (LoginService.IsLoggedIn)
|
||||
else if (AuthService.IsLoggedIn)
|
||||
{
|
||||
User = LoginService.GetLoggedInUser();
|
||||
User = await Client.GetFromJsonAsync<User>($"api/Users/{Baid}");
|
||||
}
|
||||
}
|
||||
|
||||
@ -61,7 +56,7 @@ public partial class AccessCode
|
||||
{ x => x.AccessCode, accessCode }
|
||||
};
|
||||
|
||||
var dialog = DialogService.Show<AccessCodeDeleteConfirmDialog>("Delete Access Code", parameters);
|
||||
var dialog = await DialogService.ShowAsync<AccessCodeDeleteConfirmDialog>("Delete Access Code", parameters);
|
||||
var result = await dialog.Result;
|
||||
|
||||
if (result.Canceled) return;
|
||||
@ -72,55 +67,53 @@ public partial class AccessCode
|
||||
|
||||
private async Task OnBind()
|
||||
{
|
||||
if (response != null)
|
||||
if (User == null) return;
|
||||
var result = await AuthService.BindAccessCode(inputAccessCode.ToUpper().Trim(), User);
|
||||
switch (result)
|
||||
{
|
||||
var result = await LoginService.BindAccessCode(inputAccessCode.ToUpper().Trim(), response.Users.First(u => u.Baid == Baid), Client);
|
||||
switch (result)
|
||||
{
|
||||
case 0:
|
||||
await DialogService.ShowMessageBox(
|
||||
"Error",
|
||||
(MarkupString)
|
||||
"Not logged in.<br />Please log in first and try again.",
|
||||
"Ok");
|
||||
break;
|
||||
case 1:
|
||||
await DialogService.ShowMessageBox(
|
||||
"Success",
|
||||
"New access code bound successfully.",
|
||||
"Ok");
|
||||
await InitializeUser();
|
||||
NavigationManager.NavigateTo(NavigationManager.Uri);
|
||||
break;
|
||||
case 2:
|
||||
await DialogService.ShowMessageBox(
|
||||
"Error",
|
||||
(MarkupString)
|
||||
"Bound access code upper limit reached.<br />Please delete one access code first.",
|
||||
"Ok");
|
||||
break;
|
||||
case 3:
|
||||
await DialogService.ShowMessageBox(
|
||||
"Error",
|
||||
(MarkupString)
|
||||
"Access code already bound.<br />Please delete it from the bound user first.",
|
||||
"Ok");
|
||||
break;
|
||||
case 4:
|
||||
await DialogService.ShowMessageBox(
|
||||
"Error",
|
||||
(MarkupString)
|
||||
"Access code cannot be empty.<br />Please enter a valid access code.",
|
||||
"Ok");
|
||||
break;
|
||||
case 5:
|
||||
await DialogService.ShowMessageBox(
|
||||
"Error",
|
||||
(MarkupString)
|
||||
"You can't do that!<br />You need to be an admin to edit someone else's access codes.",
|
||||
"Ok");
|
||||
break;
|
||||
}
|
||||
case 0:
|
||||
await DialogService.ShowMessageBox(
|
||||
"Error",
|
||||
(MarkupString)
|
||||
"Not logged in.<br />Please log in first and try again.",
|
||||
"Ok");
|
||||
break;
|
||||
case 1:
|
||||
await DialogService.ShowMessageBox(
|
||||
"Success",
|
||||
"New access code bound successfully.",
|
||||
"Ok");
|
||||
await InitializeUser();
|
||||
NavigationManager.NavigateTo(NavigationManager.Uri);
|
||||
break;
|
||||
case 2:
|
||||
await DialogService.ShowMessageBox(
|
||||
"Error",
|
||||
(MarkupString)
|
||||
"Bound access code upper limit reached.<br />Please delete one access code first.",
|
||||
"Ok");
|
||||
break;
|
||||
case 3:
|
||||
await DialogService.ShowMessageBox(
|
||||
"Error",
|
||||
(MarkupString)
|
||||
"Access code already bound.<br />Please delete it from the bound user first.",
|
||||
"Ok");
|
||||
break;
|
||||
case 4:
|
||||
await DialogService.ShowMessageBox(
|
||||
"Error",
|
||||
(MarkupString)
|
||||
"Access code cannot be empty.<br />Please enter a valid access code.",
|
||||
"Ok");
|
||||
break;
|
||||
case 5:
|
||||
await DialogService.ShowMessageBox(
|
||||
"Error",
|
||||
(MarkupString)
|
||||
"You can't do that!<br />You need to be an admin to edit someone else's access codes.",
|
||||
"Ok");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,17 +1,17 @@
|
||||
@inject HttpClient Client
|
||||
@inject IDialogService DialogService
|
||||
@inject LoginService LoginService
|
||||
@inject AuthService AuthService
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
@page "/ChangePassword"
|
||||
|
||||
@if (LoginService.OnlyAdmin || !LoginService.LoginRequired)
|
||||
@if (AuthService.OnlyAdmin || !AuthService.LoginRequired)
|
||||
{
|
||||
NavigationManager.NavigateTo("/");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (LoginService.IsLoggedIn)
|
||||
if (AuthService.IsLoggedIn)
|
||||
{
|
||||
<MudContainer>
|
||||
<MudGrid Justify="Justify.Center">
|
||||
|
@ -8,70 +8,63 @@ public partial class ChangePassword
|
||||
private string newPassword = "";
|
||||
private string oldPassword = "";
|
||||
|
||||
private DashboardResponse? response;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await base.OnInitializedAsync();
|
||||
response = await Client.GetFromJsonAsync<DashboardResponse>("api/Dashboard");
|
||||
}
|
||||
|
||||
private async Task OnChangePassword()
|
||||
{
|
||||
if (response != null)
|
||||
var result = await AuthService.ChangePassword(cardNum, oldPassword, newPassword, confirmNewPassword);
|
||||
switch (result)
|
||||
{
|
||||
var result = await LoginService.ChangePassword(cardNum, oldPassword, newPassword, confirmNewPassword,
|
||||
response, Client);
|
||||
switch (result)
|
||||
{
|
||||
case 0:
|
||||
await DialogService.ShowMessageBox(
|
||||
Localizer["Error"],
|
||||
"Only admin can log in.",
|
||||
Localizer["Dialog OK"]);
|
||||
NavigationManager.NavigateTo("/Users");
|
||||
break;
|
||||
case 1:
|
||||
await DialogService.ShowMessageBox(
|
||||
Localizer["Success"],
|
||||
"Password changed successfully.",
|
||||
Localizer["Dialog OK"]);
|
||||
NavigationManager.NavigateTo("/Users");
|
||||
break;
|
||||
case 2:
|
||||
await DialogService.ShowMessageBox(
|
||||
Localizer["Error"],
|
||||
"Confirm new password is not the same as new password.",
|
||||
Localizer["Dialog OK"]);
|
||||
break;
|
||||
case 3:
|
||||
await DialogService.ShowMessageBox(
|
||||
Localizer["Error"],
|
||||
(MarkupString)
|
||||
"Card number not found.<br />Please play one game with this card number to register it.",
|
||||
Localizer["Dialog OK"]);
|
||||
break;
|
||||
case 4:
|
||||
await DialogService.ShowMessageBox(
|
||||
Localizer["Error"],
|
||||
(MarkupString)
|
||||
"Old password is wrong!",
|
||||
Localizer["Dialog OK"]);
|
||||
break;
|
||||
case 5:
|
||||
await DialogService.ShowMessageBox(
|
||||
Localizer["Error"],
|
||||
(MarkupString)
|
||||
"Card number not registered.<br />Please use register button to create a password first.",
|
||||
Localizer["Dialog OK"]);
|
||||
break;
|
||||
case 6:
|
||||
await DialogService.ShowMessageBox(
|
||||
Localizer["Error"],
|
||||
Localizer["Unknown Error"],
|
||||
Localizer["Dialog OK"]);
|
||||
break;
|
||||
}
|
||||
case 0:
|
||||
await DialogService.ShowMessageBox(
|
||||
Localizer["Error"],
|
||||
"Only admin can log in.",
|
||||
Localizer["Dialog OK"]);
|
||||
NavigationManager.NavigateTo("/Users");
|
||||
break;
|
||||
case 1:
|
||||
await DialogService.ShowMessageBox(
|
||||
Localizer["Success"],
|
||||
"Password changed successfully.",
|
||||
Localizer["Dialog OK"]);
|
||||
NavigationManager.NavigateTo("/Users");
|
||||
break;
|
||||
case 2:
|
||||
await DialogService.ShowMessageBox(
|
||||
Localizer["Error"],
|
||||
"Confirm new password is not the same as new password.",
|
||||
Localizer["Dialog OK"]);
|
||||
break;
|
||||
case 3:
|
||||
await DialogService.ShowMessageBox(
|
||||
Localizer["Error"],
|
||||
(MarkupString)
|
||||
"Card number not found.<br />Please play one game with this card number to register it.",
|
||||
Localizer["Dialog OK"]);
|
||||
break;
|
||||
case 4:
|
||||
await DialogService.ShowMessageBox(
|
||||
Localizer["Error"],
|
||||
(MarkupString)
|
||||
"Old password is wrong!",
|
||||
Localizer["Dialog OK"]);
|
||||
break;
|
||||
case 5:
|
||||
await DialogService.ShowMessageBox(
|
||||
Localizer["Error"],
|
||||
(MarkupString)
|
||||
"Card number not registered.<br />Please use register button to create a password first.",
|
||||
Localizer["Dialog OK"]);
|
||||
break;
|
||||
case 6:
|
||||
await DialogService.ShowMessageBox(
|
||||
Localizer["Error"],
|
||||
Localizer["Unknown Error"],
|
||||
Localizer["Dialog OK"]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,14 +1,14 @@
|
||||
@inject IGameDataService GameDataService
|
||||
@inject HttpClient Client
|
||||
@inject LoginService LoginService
|
||||
@inject IJSRuntime JSRuntime
|
||||
@inject AuthService AuthService
|
||||
@inject IJSRuntime JsRuntime
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
@page "/Users/{baid:int}/DaniDojo"
|
||||
|
||||
@if (LoginService.LoginRequired && (!LoginService.IsLoggedIn || (LoginService.GetLoggedInUser().Baid != Baid && !LoginService.IsAdmin)))
|
||||
@if (AuthService.LoginRequired && (!AuthService.IsLoggedIn || (AuthService.GetLoggedInBaid() != Baid && !AuthService.IsAdmin)))
|
||||
{
|
||||
if (!LoginService.IsLoggedIn)
|
||||
if (!AuthService.IsLoggedIn)
|
||||
{
|
||||
NavigationManager.NavigateTo("/Login");
|
||||
}
|
||||
@ -370,7 +370,7 @@ else
|
||||
var redRequirement = GetSongBorderCondition(border, songNumber, false);
|
||||
var goldRequirement = GetSongBorderCondition(border, songNumber, true);
|
||||
var barClass = "bar-default";
|
||||
var resultText = @Localizer["Not Cleared"];
|
||||
var resultText = Localizer["Not Cleared"];
|
||||
|
||||
<MudItem xs="12" md="4">
|
||||
<MudCard Outlined="true">
|
||||
@ -381,7 +381,7 @@ else
|
||||
</MudCardHeader>
|
||||
<MudCardContent>
|
||||
<MudText Typo="Typo.subtitle2" Style="font-weight:bold;">@Localizer["Result"]</MudText>
|
||||
@if (bestDataMap.TryGetValue(danId, out var danBestData))
|
||||
@if (bestDataMap.TryGetValue(danId, out var danBestData) && (danBestData.DanBestStageDataList.Count > songNumber))
|
||||
{
|
||||
var bestData = GetSongBestFromData((DanConditionType)border.OdaiType, danBestData, songNumber);
|
||||
if ((DanConditionType)border.OdaiType is DanConditionType.BadCount or DanConditionType.OkCount)
|
||||
@ -389,14 +389,15 @@ else
|
||||
if (bestData <= redRequirement)
|
||||
{
|
||||
barClass = "bar-pass-red";
|
||||
resultText = @Localizer["Pass"];
|
||||
resultText = Localizer["Pass"];
|
||||
}
|
||||
|
||||
if (bestData <= goldRequirement)
|
||||
{
|
||||
barClass = "bar-pass-gold";
|
||||
resultText = @Localizer["Gold"];
|
||||
resultText = Localizer["Gold"];
|
||||
}
|
||||
|
||||
var resultValue = redRequirement - bestData;
|
||||
if (bestData >= redRequirement) resultValue = 0;
|
||||
|
||||
@ -410,13 +411,13 @@ else
|
||||
if (bestData >= redRequirement)
|
||||
{
|
||||
barClass = "bar-pass-red";
|
||||
resultText = @Localizer["Pass"];
|
||||
resultText = Localizer["Pass"];
|
||||
}
|
||||
|
||||
if (bestData >= goldRequirement)
|
||||
{
|
||||
barClass = "bar-pass-gold";
|
||||
resultText = @Localizer["Gold"];
|
||||
resultText = Localizer["Gold"];
|
||||
}
|
||||
|
||||
<MudProgressLinear Class="@barClass" Rounded="true" Size="Size.Large" Max="@(goldRequirement > 0 ? goldRequirement : 1)" Value="@(goldRequirement > 0 ? bestData : 1)">
|
||||
@ -426,7 +427,6 @@ else
|
||||
<MudText Typo="Typo.caption">@resultText</MudText>
|
||||
</MudStack>
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -25,11 +25,11 @@ public partial class DaniDojo
|
||||
.Sort((stageData, otherStageData) => stageData.SongNumber.CompareTo(otherStageData.SongNumber)));
|
||||
bestDataMap = response.DanBestDataList.ToDictionary(data => data.DanId);
|
||||
|
||||
CurrentLanguage = await JSRuntime.InvokeAsync<string>("blazorCulture.get");
|
||||
CurrentLanguage = await JsRuntime.InvokeAsync<string>("blazorCulture.get");
|
||||
|
||||
userSetting = await Client.GetFromJsonAsync<UserSetting>($"api/UserSettings/{Baid}");
|
||||
|
||||
if (LoginService.IsLoggedIn && !LoginService.IsAdmin)
|
||||
if (AuthService.IsLoggedIn && !AuthService.IsAdmin)
|
||||
{
|
||||
breadcrumbs.Add(new BreadcrumbItem(Localizer["Dashboard"], href: "/"));
|
||||
}
|
||||
@ -98,6 +98,7 @@ public partial class DaniDojo
|
||||
private static uint GetSongBestFromData(DanConditionType type, DanBestData data, int songNumber)
|
||||
{
|
||||
songNumber.Throw().IfOutOfRange(0, 2);
|
||||
|
||||
return type switch
|
||||
{
|
||||
DanConditionType.SoulGauge => throw new ArgumentException("Soul gauge should not be here"),
|
||||
|
@ -16,7 +16,7 @@
|
||||
<MudText>
|
||||
<code>
|
||||
<pre>
|
||||
@String.Format("{0:0000 0000 0000 0000 0000}", (Int64.Parse(AccessCode)))
|
||||
@AccessCode
|
||||
</pre>
|
||||
</code>
|
||||
</MudText>
|
||||
|
@ -1,16 +1,25 @@
|
||||
namespace TaikoWebUI.Pages.Dialogs;
|
||||
using System.Net.Http.Headers;
|
||||
using Blazored.LocalStorage;
|
||||
|
||||
namespace TaikoWebUI.Pages.Dialogs;
|
||||
|
||||
public partial class AccessCodeDeleteConfirmDialog
|
||||
{
|
||||
|
||||
[CascadingParameter]
|
||||
MudDialogInstance MudDialog { get; set; } = null!;
|
||||
[CascadingParameter] private MudDialogInstance MudDialog { get; set; } = null!;
|
||||
|
||||
[Parameter]
|
||||
public User User { get; set; } = new();
|
||||
|
||||
[Parameter]
|
||||
public string AccessCode { get; set; } = "";
|
||||
|
||||
[Inject]
|
||||
public ILocalStorageService LocalStorage { get; set; } = null!;
|
||||
|
||||
[Inject]
|
||||
public AuthService AuthService { get; set; } = null!;
|
||||
|
||||
|
||||
private void Cancel() => MudDialog.Cancel();
|
||||
|
||||
|
@ -64,6 +64,9 @@
|
||||
|
||||
[Parameter]
|
||||
public bool AllowFreeProfileEditing { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public List<uint> TitleUniqueIdList { get; set; } = new();
|
||||
|
||||
private IEnumerable<Title> titles = new List<Title>();
|
||||
|
||||
@ -78,7 +81,12 @@
|
||||
if (!AllowFreeProfileEditing)
|
||||
{
|
||||
var unlockedTitle = UserSetting.UnlockedTitle;
|
||||
titleSet = titleSet.Where(title => unlockedTitle.Contains((uint)title.TitleId)).ToImmutableHashSet();
|
||||
titleSet = titleSet.Where(title => unlockedTitle.Contains(title.TitleId)).ToImmutableHashSet();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Only allow titles in titleUniqueIdList
|
||||
titleSet = titleSet.Where(title => TitleUniqueIdList.Contains(title.TitleId)).ToImmutableHashSet();
|
||||
}
|
||||
titles = titleSet.ToImmutableList().Sort((title, title1) => title.TitleId.CompareTo(title1.TitleId));
|
||||
var currentTitle = new Title
|
||||
|
@ -1,4 +1,7 @@
|
||||
namespace TaikoWebUI.Pages.Dialogs;
|
||||
using System.Net.Http.Headers;
|
||||
using Blazored.LocalStorage;
|
||||
|
||||
namespace TaikoWebUI.Pages.Dialogs;
|
||||
|
||||
public partial class ResetPasswordConfirmDialog
|
||||
{
|
||||
@ -6,6 +9,12 @@ public partial class ResetPasswordConfirmDialog
|
||||
|
||||
[Parameter]
|
||||
public User User { get; set; } = new();
|
||||
|
||||
[Inject]
|
||||
public ILocalStorageService LocalStorage { get; set; } = null!;
|
||||
|
||||
[Inject]
|
||||
public AuthService AuthService { get; set; } = null!;
|
||||
|
||||
private void Cancel() => MudDialog.Cancel();
|
||||
|
||||
|
@ -1,14 +1,21 @@
|
||||
namespace TaikoWebUI.Pages.Dialogs;
|
||||
using System.Net.Http.Headers;
|
||||
using Blazored.LocalStorage;
|
||||
|
||||
namespace TaikoWebUI.Pages.Dialogs;
|
||||
|
||||
public partial class UserDeleteConfirmDialog
|
||||
{
|
||||
|
||||
[CascadingParameter]
|
||||
MudDialogInstance MudDialog { get; set; } = null!;
|
||||
[CascadingParameter] private MudDialogInstance MudDialog { get; set; } = null!;
|
||||
|
||||
[Parameter]
|
||||
public User User { get; set; } = new();
|
||||
|
||||
[Inject]
|
||||
public ILocalStorageService LocalStorage { get; set; } = null!;
|
||||
|
||||
[Inject]
|
||||
public AuthService AuthService { get; set; } = null!;
|
||||
|
||||
private void Cancel() => MudDialog.Cancel();
|
||||
|
||||
private async Task DeleteUser()
|
||||
|
@ -1,6 +1,4 @@
|
||||
@inject IGameDataService GameDataService
|
||||
|
||||
<MudDialog Class="dialog-user-qr-code">
|
||||
<MudDialog Class="dialog-user-qr-code">
|
||||
<DialogContent>
|
||||
<MudExtensions.MudBarcode Value="@qrCode" BarcodeFormat="ZXing.BarcodeFormat.QR_CODE" Height="300" Width="300" />
|
||||
</DialogContent>
|
||||
|
@ -1,7 +1,7 @@
|
||||
@inject IGameDataService GameDataService
|
||||
@inject HttpClient Client
|
||||
@inject LoginService LoginService
|
||||
@inject IJSRuntime JSRuntime
|
||||
@inject AuthService AuthService
|
||||
@inject IJSRuntime JsRuntime
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject Blazored.LocalStorage.ILocalStorageService LocalStorage
|
||||
@using TaikoWebUI.Utilities;
|
||||
@ -20,9 +20,9 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
@if (LoginService.LoginRequired && (!LoginService.IsLoggedIn || (LoginService.GetLoggedInUser().Baid != Baid && !LoginService.IsAdmin)))
|
||||
@if (AuthService.LoginRequired && (!AuthService.IsLoggedIn || (AuthService.GetLoggedInBaid() != Baid && !AuthService.IsAdmin)))
|
||||
{
|
||||
if (!LoginService.IsLoggedIn)
|
||||
if (!AuthService.IsLoggedIn)
|
||||
{
|
||||
NavigationManager.NavigateTo("/Login");
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
using System;
|
||||
using Microsoft.JSInterop;
|
||||
|
||||
|
||||
namespace TaikoWebUI.Pages;
|
||||
|
||||
public partial class HighScores
|
||||
@ -24,7 +25,7 @@ public partial class HighScores
|
||||
|
||||
userSetting = await Client.GetFromJsonAsync<UserSetting>($"api/UserSettings/{Baid}");
|
||||
|
||||
var language = await JSRuntime.InvokeAsync<string>("blazorCulture.get");
|
||||
var language = await JsRuntime.InvokeAsync<string>("blazorCulture.get");
|
||||
|
||||
response.SongBestData.ForEach(data =>
|
||||
{
|
||||
@ -42,13 +43,12 @@ public partial class HighScores
|
||||
songBestDataList.Sort((data1, data2) => GameDataService.GetMusicIndexBySongId(data1.SongId)
|
||||
.CompareTo(GameDataService.GetMusicIndexBySongId(data2.SongId)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Set last selected tab from local storage
|
||||
selectedDifficultyTab = await localStorage.GetItemAsync<int>($"highScoresTab");
|
||||
selectedDifficultyTab = await LocalStorage.GetItemAsync<int>($"highScoresTab");
|
||||
|
||||
// Breadcrumbs
|
||||
if (LoginService.IsLoggedIn && !LoginService.IsAdmin)
|
||||
if (AuthService.IsLoggedIn && !AuthService.IsAdmin)
|
||||
{
|
||||
breadcrumbs.Add(new BreadcrumbItem(Localizer["Dashboard"], href: "/"));
|
||||
}
|
||||
@ -78,6 +78,6 @@ public partial class HighScores
|
||||
private async Task OnTabChanged(int index)
|
||||
{
|
||||
selectedDifficultyTab = index;
|
||||
await localStorage.SetItemAsync($"highScoresTab", selectedDifficultyTab);
|
||||
await LocalStorage.SetItemAsync($"highScoresTab", selectedDifficultyTab);
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
@inject HttpClient Client
|
||||
@inject IDialogService DialogService
|
||||
@inject LoginService LoginService
|
||||
@inject AuthService AuthService
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
@page "/Login"
|
||||
|
||||
|
||||
@if (!LoginService.IsLoggedIn)
|
||||
@if (!AuthService.IsLoggedIn)
|
||||
{
|
||||
// Not logged in, show login form
|
||||
<MudContainer>
|
||||
|
@ -5,59 +5,54 @@ public partial class Login
|
||||
private string inputAccessCode = "";
|
||||
private MudForm loginForm = default!;
|
||||
private string inputPassword = "";
|
||||
private DashboardResponse? response;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await base.OnInitializedAsync();
|
||||
response = await Client.GetFromJsonAsync<DashboardResponse>("api/Dashboard");
|
||||
}
|
||||
|
||||
private async Task OnLogin()
|
||||
{
|
||||
if (response != null)
|
||||
var result = await AuthService.Login(inputAccessCode, inputPassword);
|
||||
var options = new DialogOptions { DisableBackdropClick = true };
|
||||
switch (result)
|
||||
{
|
||||
var result = await LoginService.Login(inputAccessCode, inputPassword, Client);
|
||||
var options = new DialogOptions { DisableBackdropClick = true };
|
||||
switch (result)
|
||||
{
|
||||
case 0:
|
||||
await DialogService.ShowMessageBox(
|
||||
Localizer["Error"],
|
||||
"Only admin can log in.",
|
||||
Localizer["Dialog OK"], null, null, options);
|
||||
await loginForm.ResetAsync();
|
||||
break;
|
||||
case 1:
|
||||
NavigationManager.NavigateTo("/Users");
|
||||
break;
|
||||
case 2:
|
||||
await DialogService.ShowMessageBox(
|
||||
Localizer["Error"],
|
||||
"Wrong password!",
|
||||
Localizer["Dialog OK"], null, null, options);
|
||||
break;
|
||||
case 3:
|
||||
await DialogService.ShowMessageBox(
|
||||
Localizer["Error"],
|
||||
(MarkupString)
|
||||
"Access code not found.<br />Please play one game with this access code to register it.",
|
||||
Localizer["Dialog OK"], null, null, options);
|
||||
break;
|
||||
case 4:
|
||||
await DialogService.ShowMessageBox(
|
||||
Localizer["Error"],
|
||||
(MarkupString)
|
||||
"Access code not registered.<br />Please use register button to create a password first.",
|
||||
Localizer["Dialog OK"], null, null, options);
|
||||
break;
|
||||
case 5:
|
||||
await DialogService.ShowMessageBox(
|
||||
Localizer["Error"],
|
||||
Localizer["Unknown Error"],
|
||||
Localizer["Dialog OK"], null, null, options);
|
||||
break;
|
||||
}
|
||||
case 0:
|
||||
await DialogService.ShowMessageBox(
|
||||
Localizer["Error"],
|
||||
"Only admin can log in.",
|
||||
Localizer["Dialog OK"], null, null, options);
|
||||
await loginForm.ResetAsync();
|
||||
break;
|
||||
case 1:
|
||||
NavigationManager.NavigateTo("/Users");
|
||||
break;
|
||||
case 2:
|
||||
await DialogService.ShowMessageBox(
|
||||
Localizer["Error"],
|
||||
"Wrong password!",
|
||||
Localizer["Dialog OK"], null, null, options);
|
||||
break;
|
||||
case 3:
|
||||
await DialogService.ShowMessageBox(
|
||||
Localizer["Error"],
|
||||
(MarkupString)
|
||||
"Access code not found.<br />Please play one game with this access code to register it.",
|
||||
Localizer["Dialog OK"], null, null, options);
|
||||
break;
|
||||
case 4:
|
||||
await DialogService.ShowMessageBox(
|
||||
Localizer["Error"],
|
||||
(MarkupString)
|
||||
"Access code not registered.<br />Please use register button to create a password first.",
|
||||
Localizer["Dialog OK"], null, null, options);
|
||||
break;
|
||||
case 5:
|
||||
await DialogService.ShowMessageBox(
|
||||
Localizer["Error"],
|
||||
Localizer["Unknown Error"],
|
||||
Localizer["Dialog OK"], null, null, options);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
@inject IGameDataService GameDataService
|
||||
@inject HttpClient Client
|
||||
@inject LoginService LoginService
|
||||
@inject AuthService AuthService
|
||||
@inject IJSRuntime JSRuntime
|
||||
@inject NavigationManager NavigationManager
|
||||
@using TaikoWebUI.Utilities;
|
||||
@ -21,7 +21,7 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
@if (LoginService.LoginRequired && (!LoginService.IsLoggedIn || (LoginService.GetLoggedInUser().Baid != Baid && !LoginService.IsAdmin)))
|
||||
@if (AuthService.LoginRequired && (!AuthService.IsLoggedIn || (AuthService.GetLoggedInBaid() != Baid && !AuthService.IsAdmin)))
|
||||
{
|
||||
<MudItem xs="12">
|
||||
<MudText Align="Align.Center" Class="my-8">
|
||||
@ -135,7 +135,7 @@
|
||||
</MudTable>
|
||||
</ChildRowContent>
|
||||
<PagerContent>
|
||||
<MudTablePager RowsPerPageString=@Localizer["Rows Per Page"] />
|
||||
<MudTablePager RowsPerPageString=@Localizer["Rows Per Page:"] />
|
||||
</PagerContent>
|
||||
</MudTable>
|
||||
</MudItem>
|
||||
|
@ -16,7 +16,7 @@ public partial class PlayHistory
|
||||
|
||||
private string Search { get; set; } = string.Empty;
|
||||
|
||||
private string? CurrentLanguage;
|
||||
private string? currentLanguage;
|
||||
|
||||
private SongHistoryResponse? response;
|
||||
|
||||
@ -31,14 +31,14 @@ public partial class PlayHistory
|
||||
response = await Client.GetFromJsonAsync<SongHistoryResponse>($"api/PlayHistory/{(uint)Baid}");
|
||||
response.ThrowIfNull();
|
||||
|
||||
CurrentLanguage = await JSRuntime.InvokeAsync<string>("blazorCulture.get");
|
||||
currentLanguage = await JSRuntime.InvokeAsync<string>("blazorCulture.get");
|
||||
|
||||
response.SongHistoryData.ForEach(data =>
|
||||
{
|
||||
var songId = data.SongId;
|
||||
data.Genre = GameDataService.GetMusicGenreBySongId(songId);
|
||||
data.MusicName = GameDataService.GetMusicNameBySongId(songId, string.IsNullOrEmpty(CurrentLanguage) ? "ja" : CurrentLanguage);
|
||||
data.MusicArtist = GameDataService.GetMusicArtistBySongId(songId, string.IsNullOrEmpty(CurrentLanguage) ? "ja" : CurrentLanguage);
|
||||
data.MusicName = GameDataService.GetMusicNameBySongId(songId, string.IsNullOrEmpty(currentLanguage) ? "ja" : currentLanguage);
|
||||
data.MusicArtist = GameDataService.GetMusicArtistBySongId(songId, string.IsNullOrEmpty(currentLanguage) ? "ja" : currentLanguage);
|
||||
data.Stars = GameDataService.GetMusicStarLevel(songId, data.Difficulty);
|
||||
data.ShowDetails = false;
|
||||
});
|
||||
@ -134,7 +134,7 @@ public partial class PlayHistory
|
||||
return true;
|
||||
}
|
||||
|
||||
var language = CurrentLanguage ?? "ja";
|
||||
var language = currentLanguage ?? "ja";
|
||||
|
||||
if (songHistoryDataList[0].PlayTime
|
||||
.ToString("dddd d MMMM yyyy - HH:mm", CultureInfo.CreateSpecificCulture(language))
|
||||
|
@ -2,15 +2,15 @@
|
||||
@inject HttpClient Client
|
||||
@inject IGameDataService GameDataService
|
||||
@inject IDialogService DialogService
|
||||
@inject LoginService LoginService
|
||||
@inject IJSRuntime Js
|
||||
@inject AuthService AuthService
|
||||
@inject IJSRuntime JsRuntime
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
@if (response is not null)
|
||||
{
|
||||
@if (LoginService.LoginRequired && (!LoginService.IsLoggedIn || (LoginService.GetLoggedInUser().Baid != Baid && !LoginService.IsAdmin)))
|
||||
@if (AuthService.LoginRequired && (!AuthService.IsLoggedIn || (AuthService.GetLoggedInBaid() != Baid && !AuthService.IsAdmin)))
|
||||
{
|
||||
if (!LoginService.IsLoggedIn)
|
||||
if (!AuthService.IsLoggedIn)
|
||||
{
|
||||
NavigationManager.NavigateTo("/Login");
|
||||
}
|
||||
@ -47,7 +47,7 @@
|
||||
|
||||
<MudGrid>
|
||||
<MudItem xs="12" md="8">
|
||||
@if (LoginService.AllowFreeProfileEditing)
|
||||
@if (AuthService.AllowFreeProfileEditing)
|
||||
{
|
||||
<MudTextField TextChanged="UpdateTitle" @bind-Value="@response.Title" Label=@Localizer["Title"]/>
|
||||
}
|
||||
@ -55,18 +55,17 @@
|
||||
{
|
||||
<MudTextField ReadOnly="true" @bind-Value="@response.Title" Label=@Localizer["Title"]/>
|
||||
}
|
||||
<MudButton Color="Color.Primary" Class="mt-1" Size="Size.Small" OnClick="@((_) => OpenChooseTitleDialog())">
|
||||
<MudButton Color="Color.Primary" Class="mt-1" Size="Size.Small" OnClick="@(_ => OpenChooseTitleDialog())">
|
||||
@Localizer["Select a Title"]
|
||||
</MudButton>
|
||||
</MudItem>
|
||||
@if (LoginService.AllowFreeProfileEditing)
|
||||
@if (AuthService.AllowFreeProfileEditing)
|
||||
{
|
||||
<MudItem xs="12" md="4">
|
||||
<MudSelect @bind-Value="@response.TitlePlateId" Label=@Localizer["Title Plate"]>
|
||||
@for (uint i = 0; i < TitlePlateStrings.Length; i++)
|
||||
@foreach (var index in titlePlateIdList)
|
||||
{
|
||||
var index = i;
|
||||
<MudSelectItem Value="@i">@TitlePlateStrings[index]</MudSelectItem>
|
||||
<MudSelectItem Value="@index">@TitlePlateStrings[index]</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
@ -402,17 +401,17 @@ else
|
||||
@code {
|
||||
private async Task UpdateMyDonName()
|
||||
{
|
||||
@if (response is not null) await Js.InvokeVoidAsync("updateMyDonNameText", response.MyDonName);
|
||||
@if (response is not null) await JsRuntime.InvokeVoidAsync("updateMyDonNameText", response.MyDonName);
|
||||
}
|
||||
|
||||
private async Task UpdateTitle()
|
||||
{
|
||||
@if (response is not null) await Js.InvokeVoidAsync("updateTitleText", response.Title);
|
||||
@if (response is not null) await JsRuntime.InvokeVoidAsync("updateTitleText", response.Title);
|
||||
}
|
||||
|
||||
private async Task UpdateScoreboard(Difficulty difficulty)
|
||||
{
|
||||
UpdateScores(difficulty);
|
||||
await Js.InvokeVoidAsync("updateScoreboardText", scoresArray);
|
||||
await JsRuntime.InvokeVoidAsync("updateScoreboardText", scoresArray);
|
||||
}
|
||||
}
|
@ -187,7 +187,7 @@ public partial class Profile
|
||||
response = await Client.GetFromJsonAsync<UserSetting>($"api/UserSettings/{Baid}");
|
||||
response.ThrowIfNull();
|
||||
|
||||
if (LoginService.IsLoggedIn && !LoginService.IsAdmin)
|
||||
if (AuthService.IsLoggedIn && !AuthService.IsAdmin)
|
||||
{
|
||||
breadcrumbs.Add(new BreadcrumbItem(Localizer["Dashboard"], href: "/"));
|
||||
}
|
||||
@ -238,7 +238,7 @@ public partial class Profile
|
||||
var unlockedFace = response != null ? response.UnlockedFace : new List<uint>();
|
||||
var unlockedPuchi = response != null ? response.UnlockedPuchi : new List<uint>();
|
||||
|
||||
if (LoginService.AllowFreeProfileEditing)
|
||||
if (AuthService.AllowFreeProfileEditing)
|
||||
{
|
||||
kigurumiUniqueIdList = GameDataService.GetKigurumiUniqueIdList();
|
||||
headUniqueIdList = GameDataService.GetHeadUniqueIdList();
|
||||
@ -272,7 +272,7 @@ public partial class Profile
|
||||
|
||||
private void InitializeAvailableTitlePlates()
|
||||
{
|
||||
titlePlateIdList = GameDataService.GetTitlePlateIdList().Except(GameDataService.GetLockedTitlePlateIdList()).ToList();
|
||||
titlePlateIdList = GameDataService.GetTitlePlateIdList().ToList();
|
||||
// Cut off ids longer than TitlePlateStrings
|
||||
titlePlateIdList = titlePlateIdList.Where(id => id < TitlePlateStrings.Length).Except(GameDataService.GetLockedTitlePlateIdList()).ToList();
|
||||
}
|
||||
@ -283,15 +283,18 @@ public partial class Profile
|
||||
|
||||
var unlockedTitle = response != null ? response.UnlockedTitle : new List<uint>();
|
||||
|
||||
if (LoginService.AllowFreeProfileEditing)
|
||||
if (AuthService.AllowFreeProfileEditing)
|
||||
{
|
||||
titleUniqueIdList = GameDataService.GetTitleUniqueIdList();
|
||||
|
||||
var titles = GameDataService.GetTitles();
|
||||
// Lock titles in LockedTitlesList but not in UnlockedTitle
|
||||
var lockedTitleUniqueIdList = GameDataService.GetLockedTitleUniqueIdList().Except(unlockedTitle).ToList();
|
||||
// Lock titles with rarity not in titlePlateIdList and not in unlockedTitle
|
||||
lockedTitleUniqueIdList.AddRange(titles.Where(title => !titlePlateIdList.Contains(title.TitleRarity) && !unlockedTitle.Contains(title.TitleId)).Select(title => title.TitleId));
|
||||
var lockedTitleUniqueIdList = GameDataService.GetLockedTitleUniqueIdList().ToList();
|
||||
var lockedTitlePlateIdList = GameDataService.GetLockedTitlePlateIdList().ToList();
|
||||
// Unlock titles in UnlockedTitlesList
|
||||
lockedTitleUniqueIdList = lockedTitleUniqueIdList.Except(unlockedTitle).ToList();
|
||||
// Find uniqueIds of titles with rarity in lockedTitlePlateIdList
|
||||
lockedTitleUniqueIdList.AddRange(titles.Where(title => lockedTitlePlateIdList.Contains(title.TitleRarity)).Select(title => title.TitleId));
|
||||
titleUniqueIdList = titleUniqueIdList.Except(lockedTitleUniqueIdList).ToList();
|
||||
}
|
||||
else
|
||||
@ -387,7 +390,8 @@ public partial class Profile
|
||||
var parameters = new DialogParameters<ChooseTitleDialog>
|
||||
{
|
||||
{x => x.UserSetting, response},
|
||||
{x => x.AllowFreeProfileEditing, LoginService.AllowFreeProfileEditing}
|
||||
{x => x.AllowFreeProfileEditing, AuthService.AllowFreeProfileEditing},
|
||||
{x => x.TitleUniqueIdList, titleUniqueIdList}
|
||||
};
|
||||
var dialog = DialogService.Show<ChooseTitleDialog>("Player Titles", parameters, options);
|
||||
var result = await dialog.Result;
|
||||
|
@ -1,16 +1,16 @@
|
||||
@inject HttpClient Client
|
||||
@inject IDialogService DialogService
|
||||
@inject LoginService LoginService
|
||||
@inject AuthService AuthService
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
@page "/Register"
|
||||
|
||||
@if (LoginService.OnlyAdmin || !LoginService.LoginRequired)
|
||||
@if (AuthService.OnlyAdmin || !AuthService.LoginRequired)
|
||||
{
|
||||
Console.WriteLine("Registration is disabled. Redirecting to Dashboard...");
|
||||
NavigationManager.NavigateTo("/");
|
||||
}
|
||||
else if (LoginService.IsLoggedIn)
|
||||
else if (AuthService.IsLoggedIn)
|
||||
{
|
||||
// User is already logged in. Redirect to dashboard.
|
||||
NavigationManager.NavigateTo("/");
|
||||
@ -30,7 +30,7 @@ else
|
||||
<MudTextField @bind-value="accessCode" InputType="InputType.Text" T="string"
|
||||
FullWidth="true" Required="@true" RequiredError=@Localizer["Access Code is required"]
|
||||
Label=@Localizer["Access Code"] Variant="Variant.Outlined" Margin="Margin.Dense" />
|
||||
@if (LoginService.RegisterWithLastPlayTime)
|
||||
@if (AuthService.RegisterWithLastPlayTime)
|
||||
{
|
||||
<MudTextField @bind-value="inviteCode" InputType="InputType.Text" T="string"
|
||||
FullWidth="true" Label=@Localizer["Invite Code (Optional)"]/>
|
||||
|
@ -12,73 +12,67 @@ public partial class Register
|
||||
private DateTime? date = DateTime.Today;
|
||||
private TimeSpan? time = new TimeSpan(00, 45, 00);
|
||||
private string inviteCode = "";
|
||||
|
||||
private DashboardResponse? response;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await base.OnInitializedAsync();
|
||||
response = await Client.GetFromJsonAsync<DashboardResponse>("api/Dashboard");
|
||||
}
|
||||
|
||||
private async Task OnRegister()
|
||||
{
|
||||
var inputDateTime = date!.Value.Date + time!.Value;
|
||||
if (response != null)
|
||||
var result = await AuthService.Register(accessCode, inputDateTime, password, confirmPassword, inviteCode);
|
||||
var options = new DialogOptions { DisableBackdropClick = true };
|
||||
switch (result)
|
||||
{
|
||||
var result = await LoginService.Register(accessCode, inputDateTime, password, confirmPassword, response, Client, inviteCode);
|
||||
var options = new DialogOptions { DisableBackdropClick = true };
|
||||
switch (result)
|
||||
{
|
||||
case 0:
|
||||
await DialogService.ShowMessageBox(
|
||||
Localizer["Error"],
|
||||
"Only admin can register.",
|
||||
Localizer["Dialog OK"], null, null, options);
|
||||
NavigationManager.NavigateTo("/");
|
||||
break;
|
||||
case 1:
|
||||
await DialogService.ShowMessageBox(
|
||||
Localizer["Success"],
|
||||
"Access code registered successfully.",
|
||||
Localizer["Dialog OK"], null, null, options);
|
||||
NavigationManager.NavigateTo("/Login");
|
||||
break;
|
||||
case 2:
|
||||
await DialogService.ShowMessageBox(
|
||||
Localizer["Error"],
|
||||
"Confirm password is not the same as password.",
|
||||
Localizer["Dialog OK"], null, null, options);
|
||||
break;
|
||||
case 3:
|
||||
await DialogService.ShowMessageBox(
|
||||
Localizer["Error"],
|
||||
(MarkupString)
|
||||
"Access code not found.<br />Please play one game with this access code to register it.",
|
||||
Localizer["Dialog OK"], null, null, options);
|
||||
break;
|
||||
case 4:
|
||||
await DialogService.ShowMessageBox(
|
||||
Localizer["Error"],
|
||||
(MarkupString)
|
||||
"Access code is already registered, please use set password to login.",
|
||||
Localizer["Dialog OK"], null, null, options);
|
||||
NavigationManager.NavigateTo("/Login");
|
||||
break;
|
||||
case 5:
|
||||
await DialogService.ShowMessageBox(
|
||||
Localizer["Error"],
|
||||
(MarkupString)
|
||||
"Wrong last play time.<br />If you have forgotten when you last played, please play another game with this access code.",
|
||||
Localizer["Dialog OK"], null, null, options);
|
||||
break;
|
||||
case 6:
|
||||
await DialogService.ShowMessageBox(
|
||||
Localizer["Error"],
|
||||
Localizer["Unknown Error"],
|
||||
Localizer["Dialog OK"], null, null, options);
|
||||
break;
|
||||
}
|
||||
case 0:
|
||||
await DialogService.ShowMessageBox(
|
||||
Localizer["Error"],
|
||||
"Only admin can register.",
|
||||
Localizer["Dialog OK"], null, null, options);
|
||||
NavigationManager.NavigateTo("/");
|
||||
break;
|
||||
case 1:
|
||||
await DialogService.ShowMessageBox(
|
||||
Localizer["Success"],
|
||||
"Access code registered successfully.",
|
||||
Localizer["Dialog OK"], null, null, options);
|
||||
NavigationManager.NavigateTo("/Login");
|
||||
break;
|
||||
case 2:
|
||||
await DialogService.ShowMessageBox(
|
||||
Localizer["Error"],
|
||||
"Confirm password is not the same as password.",
|
||||
Localizer["Dialog OK"], null, null, options);
|
||||
break;
|
||||
case 3:
|
||||
await DialogService.ShowMessageBox(
|
||||
Localizer["Error"],
|
||||
(MarkupString)
|
||||
"Access code not found.<br />Please play one game with this access code to register it.",
|
||||
Localizer["Dialog OK"], null, null, options);
|
||||
break;
|
||||
case 4:
|
||||
await DialogService.ShowMessageBox(
|
||||
Localizer["Error"],
|
||||
(MarkupString)
|
||||
"Access code is already registered, please use set password to login.",
|
||||
Localizer["Dialog OK"], null, null, options);
|
||||
NavigationManager.NavigateTo("/Login");
|
||||
break;
|
||||
case 5:
|
||||
await DialogService.ShowMessageBox(
|
||||
Localizer["Error"],
|
||||
(MarkupString)
|
||||
"Wrong last play time.<br />If you have forgotten when you last played, please play another game with this access code.",
|
||||
Localizer["Dialog OK"], null, null, options);
|
||||
break;
|
||||
case 6:
|
||||
await DialogService.ShowMessageBox(
|
||||
Localizer["Error"],
|
||||
Localizer["Unknown Error"],
|
||||
Localizer["Dialog OK"], null, null, options);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
@ -2,34 +2,26 @@
|
||||
|
||||
@inject IGameDataService GameDataService
|
||||
@inject HttpClient Client
|
||||
@inject LoginService LoginService
|
||||
@inject AuthService AuthService
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject IJSRuntime JSRuntime
|
||||
@inject IJSRuntime JsRuntime
|
||||
|
||||
@using TaikoWebUI.Utilities;
|
||||
@using TaikoWebUI.Components.Song;
|
||||
|
||||
@if (LoginService.LoginRequired && (!LoginService.IsLoggedIn || (LoginService.GetLoggedInUser().Baid != Baid && !LoginService.IsAdmin)))
|
||||
@if (AuthService.LoginRequired && (!AuthService.IsLoggedIn || (AuthService.GetLoggedInBaid() != Baid && !AuthService.IsAdmin)))
|
||||
{
|
||||
if (!LoginService.IsLoggedIn)
|
||||
{
|
||||
NavigationManager.NavigateTo("/Login");
|
||||
}
|
||||
else
|
||||
{
|
||||
NavigationManager.NavigateTo("/");
|
||||
}
|
||||
NavigationManager.NavigateTo(AuthService.IsLoggedIn ? "/" : "/Login");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (response is not null)
|
||||
{
|
||||
<MudBreadcrumbs Items="breadcrumbs" Class="p-0 mb-3"></MudBreadcrumbs>
|
||||
<MudText Typo="Typo.h5">@SongTitle</MudText>
|
||||
<MudText Typo="Typo.body2">@SongArtist</MudText>
|
||||
<MudText Typo="Typo.h5">@songTitle</MudText>
|
||||
<MudText Typo="Typo.body2">@songArtist</MudText>
|
||||
<MudGrid Class="my-4 pb-10">
|
||||
<MudItem xs="12">
|
||||
<PlayHistoryCard Items="@SongBestData?.RecentPlayData" />
|
||||
<PlayHistoryCard Items="@songHistoryData" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
}
|
||||
|
@ -11,37 +11,38 @@ namespace TaikoWebUI.Pages
|
||||
public int Baid { get; set; }
|
||||
|
||||
private UserSetting? userSetting;
|
||||
private SongBestResponse? response;
|
||||
private SongBestData? SongBestData;
|
||||
private List<BreadcrumbItem> breadcrumbs = new List<BreadcrumbItem>();
|
||||
private SongHistoryResponse? response;
|
||||
private List<SongHistoryData>? songHistoryData;
|
||||
private readonly List<BreadcrumbItem> breadcrumbs = new();
|
||||
|
||||
private string SongTitle = string.Empty;
|
||||
private string SongArtist = string.Empty;
|
||||
private string songTitle = string.Empty;
|
||||
private string songArtist = string.Empty;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await base.OnInitializedAsync();
|
||||
|
||||
response = await Client.GetFromJsonAsync<SongBestResponse>($"api/PlayData/{Baid}");
|
||||
response = await Client.GetFromJsonAsync<SongHistoryResponse>($"api/PlayHistory/{(uint)Baid}");
|
||||
response.ThrowIfNull();
|
||||
SongBestData = response.SongBestData.FirstOrDefault(x => x.SongId == SongId);
|
||||
// Get all song best data with SongId
|
||||
songHistoryData = response.SongHistoryData.Where(data => data.SongId == (uint)SongId).ToList();
|
||||
|
||||
// Get user settings
|
||||
userSetting = await Client.GetFromJsonAsync<UserSetting>($"api/UserSettings/{Baid}");
|
||||
|
||||
// Get song title and artist
|
||||
var language = await JSRuntime.InvokeAsync<string>("blazorCulture.get");
|
||||
SongTitle = GameDataService.GetMusicNameBySongId((uint)SongId, string.IsNullOrEmpty(language) ? "ja" : language);
|
||||
SongArtist = GameDataService.GetMusicArtistBySongId((uint)SongId, string.IsNullOrEmpty(language) ? "ja" : language);
|
||||
var language = await JsRuntime.InvokeAsync<string>("blazorCulture.get");
|
||||
songTitle = GameDataService.GetMusicNameBySongId((uint)SongId, string.IsNullOrEmpty(language) ? "ja" : language);
|
||||
songArtist = GameDataService.GetMusicArtistBySongId((uint)SongId, string.IsNullOrEmpty(language) ? "ja" : language);
|
||||
|
||||
// Breadcrumbs
|
||||
var _songTitle = SongTitle;
|
||||
if (_songTitle.Length > 20)
|
||||
var formattedSongTitle = songTitle;
|
||||
if (formattedSongTitle.Length > 20)
|
||||
{
|
||||
_songTitle = _songTitle.Substring(0, 20) + "...";
|
||||
formattedSongTitle = string.Concat(formattedSongTitle.AsSpan(0, 20), "...");
|
||||
}
|
||||
|
||||
if (LoginService.IsLoggedIn && !LoginService.IsAdmin)
|
||||
if (AuthService.IsLoggedIn && !AuthService.IsAdmin)
|
||||
{
|
||||
breadcrumbs.Add(new BreadcrumbItem(Localizer["Dashboard"], href: "/"));
|
||||
}
|
||||
@ -51,7 +52,7 @@ namespace TaikoWebUI.Pages
|
||||
};
|
||||
breadcrumbs.Add(new BreadcrumbItem($"{userSetting?.MyDonName}", href: null, disabled: true));
|
||||
breadcrumbs.Add(new BreadcrumbItem(Localizer["Song List"], href: $"/Users/{Baid}/Songs", disabled: false));
|
||||
breadcrumbs.Add(new BreadcrumbItem(_songTitle, href: $"/Users/{Baid}/Songs/{SongId}", disabled: false));
|
||||
breadcrumbs.Add(new BreadcrumbItem(formattedSongTitle, href: $"/Users/{Baid}/Songs/{SongId}", disabled: false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,10 @@
|
||||
@inject IGameDataService GameDataService
|
||||
@inject HttpClient Client
|
||||
@inject LoginService LoginService
|
||||
@inject IJSRuntime JSRuntime
|
||||
@inject AuthService AuthService
|
||||
@inject IJSRuntime JsRuntime
|
||||
@inject NavigationManager NavigationManager
|
||||
@using TaikoWebUI.Utilities;
|
||||
@using TaikoWebUI.Shared.Models;
|
||||
@using SharedProject.Enums;
|
||||
|
||||
@page "/Users/{baid:int}/Songs"
|
||||
|
||||
@ -21,16 +20,9 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
@if (LoginService.LoginRequired && (!LoginService.IsLoggedIn || (LoginService.GetLoggedInUser().Baid != Baid && !LoginService.IsAdmin)))
|
||||
@if (AuthService.LoginRequired && (!AuthService.IsLoggedIn || (AuthService.GetLoggedInBaid() != Baid && !AuthService.IsAdmin)))
|
||||
{
|
||||
if (!LoginService.IsLoggedIn)
|
||||
{
|
||||
NavigationManager.NavigateTo("/Login");
|
||||
}
|
||||
else
|
||||
{
|
||||
NavigationManager.NavigateTo("/");
|
||||
}
|
||||
NavigationManager.NavigateTo(AuthService.IsLoggedIn ? "/" : "/Login");
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1,4 +1,5 @@
|
||||
using Microsoft.JSInterop;
|
||||
using System.Reflection.Emit;
|
||||
using Microsoft.JSInterop;
|
||||
using TaikoWebUI.Shared.Models;
|
||||
|
||||
|
||||
@ -8,9 +9,7 @@ public partial class SongList
|
||||
{
|
||||
[Parameter]
|
||||
public int Baid { get; set; }
|
||||
|
||||
private const string IconStyle = "width:25px; height:25px;";
|
||||
|
||||
|
||||
private string Search { get; set; } = string.Empty;
|
||||
private string GenreFilter { get; set; } = string.Empty;
|
||||
private string CurrentLanguage { get; set; } = "ja";
|
||||
@ -18,8 +17,6 @@ public partial class SongList
|
||||
private SongBestResponse? response;
|
||||
private UserSetting? userSetting;
|
||||
|
||||
private Dictionary<Difficulty, List<SongBestData>> songBestDataMap = new();
|
||||
|
||||
private readonly List<BreadcrumbItem> breadcrumbs = new();
|
||||
|
||||
private List<MusicDetail> musicMap = new();
|
||||
@ -33,9 +30,9 @@ public partial class SongList
|
||||
userSetting = await Client.GetFromJsonAsync<UserSetting>($"api/UserSettings/{Baid}");
|
||||
musicMap = GameDataService.GetMusicList();
|
||||
|
||||
CurrentLanguage = await JSRuntime.InvokeAsync<string>("blazorCulture.get");
|
||||
CurrentLanguage = await JsRuntime.InvokeAsync<string>("blazorCulture.get");
|
||||
|
||||
if (LoginService.IsLoggedIn && !LoginService.IsAdmin)
|
||||
if (AuthService.IsLoggedIn && !AuthService.IsAdmin)
|
||||
{
|
||||
breadcrumbs.Add(new BreadcrumbItem(Localizer["Dashboard"], href: "/"));
|
||||
}
|
||||
@ -47,21 +44,6 @@ public partial class SongList
|
||||
breadcrumbs.Add(new BreadcrumbItem(Localizer["Song List"], href: $"/Users/{Baid}/Songs", disabled: false));
|
||||
}
|
||||
|
||||
private async Task OnFavoriteToggled(SongBestData data)
|
||||
{
|
||||
var request = new SetFavoriteRequest
|
||||
{
|
||||
Baid = (uint)Baid,
|
||||
IsFavorite = !data.IsFavorite,
|
||||
SongId = data.SongId
|
||||
};
|
||||
var result = await Client.PostAsJsonAsync("api/FavoriteSongs", request);
|
||||
if (result.IsSuccessStatusCode)
|
||||
{
|
||||
data.IsFavorite = !data.IsFavorite;
|
||||
}
|
||||
}
|
||||
|
||||
private bool FilterSongs(MusicDetail musicDetail)
|
||||
{
|
||||
var stringsToCheck = new List<string>
|
||||
|
@ -1,6 +1,5 @@
|
||||
@inject HttpClient Client
|
||||
@inject IDialogService DialogService
|
||||
@inject LoginService LoginService
|
||||
@inject AuthService AuthService
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
@using TaikoWebUI.Components
|
||||
@ -9,61 +8,44 @@
|
||||
|
||||
<MudText Typo="Typo.h4">@Localizer["Users"]</MudText>
|
||||
<MudGrid Class="my-8">
|
||||
@if (response is not null)
|
||||
{
|
||||
// Response received and users are available
|
||||
if (response.Users.Count != 0)
|
||||
{
|
||||
if (LoginService.IsAdmin || !LoginService.LoginRequired) // Admin mode, can see all users
|
||||
{
|
||||
@foreach (var user in response.Users)
|
||||
{
|
||||
<MudItem xs="12" md="6" lg="4">
|
||||
<UserCard user="user" />
|
||||
</MudItem>
|
||||
}
|
||||
@if (!AuthService.LoginRequired || (AuthService.LoginRequired && AuthService.IsAdmin)) {
|
||||
if (users == null) {
|
||||
// Loading...
|
||||
for (uint i = 0; i < 6; i++) {
|
||||
<MudItem xs="12" md="6" lg="4">
|
||||
<MudCard Outlined="true">
|
||||
<MudCardContent>
|
||||
<MudSkeleton Width="30%" Height="42px;" Class="mb-5" />
|
||||
<MudSkeleton Width="80%" />
|
||||
<MudSkeleton Width="100%" />
|
||||
</MudCardContent>
|
||||
<MudCardActions>
|
||||
<MudStack Row="true" Style="width:100%" Spacing="4" Justify="Justify.FlexEnd">
|
||||
<MudSkeleton Width="64px" Height="40px" />
|
||||
<MudSkeleton Width="64px" Height="40px" />
|
||||
</MudStack>
|
||||
</MudCardActions>
|
||||
</MudCard>
|
||||
</MudItem>
|
||||
}
|
||||
else
|
||||
} else if (users.Count > 0) {
|
||||
foreach (var user in users)
|
||||
{
|
||||
// Not admin, redirect
|
||||
@if (!LoginService.IsLoggedIn) // Not logged in, show login form
|
||||
{
|
||||
NavigationManager.NavigateTo("/Login");
|
||||
}
|
||||
else
|
||||
{
|
||||
NavigationManager.NavigateTo("/");
|
||||
}
|
||||
<MudItem xs="12" md="6" lg="4">
|
||||
<UserCard User="user" />
|
||||
</MudItem>
|
||||
}
|
||||
}
|
||||
else
|
||||
{ // No users in the database
|
||||
} else { // No users in the database
|
||||
<MudItem xs="12">
|
||||
<MudText Align="Align.Center" Class="my-8">
|
||||
@Localizer["No data."]
|
||||
</MudText>
|
||||
</MudItem>
|
||||
}
|
||||
} else
|
||||
{
|
||||
// Loading...
|
||||
@for (uint i = 0; i < 6; i++)
|
||||
{
|
||||
<MudItem xs="12" md="6" lg="4">
|
||||
<MudCard Outlined="true">
|
||||
<MudCardContent>
|
||||
<MudSkeleton Width="30%" Height="42px;" Class="mb-5" />
|
||||
<MudSkeleton Width="80%" />
|
||||
<MudSkeleton Width="100%" />
|
||||
</MudCardContent>
|
||||
<MudCardActions>
|
||||
<MudStack Row="true" Style="width:100%" Spacing="4" Justify="Justify.FlexEnd">
|
||||
<MudSkeleton Width="64px" Height="40px" />
|
||||
<MudSkeleton Width="64px" Height="40px" />
|
||||
</MudStack>
|
||||
</MudCardActions>
|
||||
</MudCard>
|
||||
</MudItem>
|
||||
}
|
||||
} else if (AuthService.LoginRequired && !AuthService.IsLoggedIn) {
|
||||
// Not logged in, redirect
|
||||
NavigationManager.NavigateTo("/Login");
|
||||
} else {
|
||||
NavigationManager.NavigateTo("/");
|
||||
}
|
||||
</MudGrid>
|
@ -2,11 +2,14 @@
|
||||
|
||||
public partial class Users
|
||||
{
|
||||
private DashboardResponse? response;
|
||||
private List<User>? users;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await base.OnInitializedAsync();
|
||||
response = await Client.GetFromJsonAsync<DashboardResponse>("api/Dashboard");
|
||||
if (AuthService.IsAdmin || !AuthService.LoginRequired)
|
||||
{
|
||||
users = await Client.GetFromJsonAsync<List<User>>("api/Users");
|
||||
}
|
||||
}
|
||||
}
|
@ -19,7 +19,7 @@ builder.Services.AddSingleton<IGameDataService, GameDataService>();
|
||||
|
||||
builder.Services.Configure<WebUiSettings>(builder.Configuration.GetSection(nameof(WebUiSettings)));
|
||||
|
||||
builder.Services.AddScoped<LoginService>();
|
||||
builder.Services.AddScoped<AuthService>();
|
||||
builder.Services.AddLocalization();
|
||||
builder.Services.AddSingleton<MudLocalizer, ResXMudLocalizer>();
|
||||
builder.Services.AddSingleton<ScoreUtils>();
|
||||
|
@ -1,4 +1,6 @@
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Diagnostics;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Security.Claims;
|
||||
using System.Text.Json;
|
||||
using Microsoft.Extensions.Options;
|
||||
@ -7,10 +9,9 @@ using Blazored.LocalStorage;
|
||||
|
||||
namespace TaikoWebUI.Services;
|
||||
|
||||
public class LoginService
|
||||
public sealed class AuthService
|
||||
{
|
||||
public event EventHandler? LoginStatusChanged;
|
||||
public delegate void LoginStatusChangedEventHandler(object? sender, EventArgs e);
|
||||
public event EventHandler? LoginStatusChanged;
|
||||
public bool LoginRequired { get; }
|
||||
public bool OnlyAdmin { get; }
|
||||
private readonly int boundAccessCodeUpperLimit;
|
||||
@ -18,11 +19,12 @@ public class LoginService
|
||||
public bool AllowUserDelete { get; }
|
||||
public bool AllowFreeProfileEditing { get; }
|
||||
public bool IsLoggedIn { get; private set; }
|
||||
private User LoggedInUser { get; set; } = new();
|
||||
private uint LoggedInBaid { get; set; }
|
||||
public bool IsAdmin { get; private set; }
|
||||
private readonly ILocalStorageService localStorage;
|
||||
|
||||
public LoginService(IOptions<WebUiSettings> settings, ILocalStorageService localStorage)
|
||||
private readonly HttpClient client;
|
||||
|
||||
public AuthService(IOptions<WebUiSettings> settings, ILocalStorageService localStorage, HttpClient client)
|
||||
{
|
||||
this.localStorage = localStorage;
|
||||
IsLoggedIn = false;
|
||||
@ -34,14 +36,24 @@ public class LoginService
|
||||
RegisterWithLastPlayTime = webUiSettings.RegisterWithLastPlayTime;
|
||||
AllowUserDelete = webUiSettings.AllowUserDelete;
|
||||
AllowFreeProfileEditing = webUiSettings.AllowFreeProfileEditing;
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
protected virtual void OnLoginStatusChanged()
|
||||
|
||||
private void OnLoginStatusChanged()
|
||||
{
|
||||
LoginStatusChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private static (uint, bool) GetBaidAndIsAdminFromToken(string authToken)
|
||||
{
|
||||
var handler = new JwtSecurityTokenHandler();
|
||||
var jwtSecurityToken = handler.ReadJwtToken(authToken);
|
||||
var baid = uint.Parse(jwtSecurityToken.Claims.First(claim => claim.Type == ClaimTypes.Name).Value);
|
||||
var isAdmin = jwtSecurityToken.Claims.First(claim => claim.Type == ClaimTypes.Role).Value == "Admin";
|
||||
return (baid, isAdmin);
|
||||
}
|
||||
|
||||
public async Task<int> Login(string inputAccessCode, string inputPassword, HttpClient client)
|
||||
public async Task<int> Login(string inputAccessCode, string inputPassword)
|
||||
{
|
||||
// strip spaces or dashes from card number
|
||||
inputAccessCode = inputAccessCode.Replace(" ", "").Replace("-", "").Replace(":", "");
|
||||
@ -51,7 +63,7 @@ public class LoginService
|
||||
AccessCode = inputAccessCode,
|
||||
Password = inputPassword
|
||||
};
|
||||
|
||||
|
||||
var responseMessage = await client.PostAsJsonAsync("api/Auth/Login", request);
|
||||
|
||||
if (!responseMessage.IsSuccessStatusCode)
|
||||
@ -76,40 +88,48 @@ public class LoginService
|
||||
var responseContent = await responseMessage.Content.ReadAsStringAsync();
|
||||
var responseJson = JsonSerializer.Deserialize<Dictionary<string, string>>(responseContent);
|
||||
if (responseJson is null) return 5;
|
||||
|
||||
|
||||
var authToken = responseJson["authToken"];
|
||||
await localStorage.SetItemAsync("authToken", authToken);
|
||||
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authToken);
|
||||
|
||||
return await LoginWithAuthToken(authToken, client) == false ? 5 : 1;
|
||||
var (baid, isAdmin) = GetBaidAndIsAdminFromToken(authToken);
|
||||
IsLoggedIn = true;
|
||||
IsAdmin = isAdmin;
|
||||
LoggedInBaid = baid;
|
||||
OnLoginStatusChanged();
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> LoginWithAuthToken(string authToken, HttpClient client)
|
||||
public async Task LoginWithAuthToken()
|
||||
{
|
||||
var handler = new JwtSecurityTokenHandler();
|
||||
var jwtSecurityToken = handler.ReadJwtToken(authToken);
|
||||
var hasAuthToken = await localStorage.ContainKeyAsync("authToken");
|
||||
if (!hasAuthToken) return;
|
||||
|
||||
// Check whether token is expired
|
||||
if (jwtSecurityToken.ValidTo < DateTime.UtcNow) return false;
|
||||
// Attempt to get JWT token from local storage
|
||||
var authToken = await localStorage.GetItemAsync<string>("authToken");
|
||||
if (authToken == null) return;
|
||||
|
||||
var baid = jwtSecurityToken.Claims.First(claim => claim.Type == ClaimTypes.Name).Value;
|
||||
var isAdmin = jwtSecurityToken.Claims.First(claim => claim.Type == ClaimTypes.Role).Value == "Admin";
|
||||
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authToken);
|
||||
var responseMessage = await client.PostAsync("api/Auth/LoginWithToken", null);
|
||||
if (!responseMessage.IsSuccessStatusCode)
|
||||
{
|
||||
// Clear JWT token
|
||||
await localStorage.RemoveItemAsync("authToken");
|
||||
return;
|
||||
}
|
||||
|
||||
var response = await client.GetFromJsonAsync<DashboardResponse>("api/Dashboard");
|
||||
var (baid, isAdmin) = GetBaidAndIsAdminFromToken(authToken);
|
||||
|
||||
var user = response?.Users.FirstOrDefault(u => u.Baid == uint.Parse(baid));
|
||||
if (user is null) return false;
|
||||
|
||||
IsLoggedIn = true;
|
||||
IsAdmin = isAdmin;
|
||||
LoggedInUser = user;
|
||||
LoggedInBaid = baid;
|
||||
OnLoginStatusChanged();
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<int> Register(string inputCardNum, DateTime inputDateTime, string inputPassword,
|
||||
string inputConfirmPassword,
|
||||
DashboardResponse response, HttpClient client, string inviteCode)
|
||||
string inputConfirmPassword, string inviteCode)
|
||||
{
|
||||
if (OnlyAdmin) return 0;
|
||||
|
||||
@ -130,7 +150,7 @@ public class LoginService
|
||||
var responseMessage = await client.PostAsJsonAsync("api/Auth/Register", request);
|
||||
|
||||
if (responseMessage.IsSuccessStatusCode) return 1;
|
||||
|
||||
|
||||
// Unauthorized, extract specific error message as json
|
||||
var responseContent = await responseMessage.Content.ReadAsStringAsync();
|
||||
var responseJson = JsonSerializer.Deserialize<Dictionary<string, string>>(responseContent);
|
||||
@ -145,25 +165,25 @@ public class LoginService
|
||||
_ => 6
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public async Task<int> ChangePassword(string inputAccessCode, string inputOldPassword, string inputNewPassword,
|
||||
string inputConfirmNewPassword, DashboardResponse response, HttpClient client)
|
||||
string inputConfirmNewPassword)
|
||||
{
|
||||
if (OnlyAdmin) return 0;
|
||||
|
||||
|
||||
if (inputNewPassword != inputConfirmNewPassword) return 2;
|
||||
|
||||
|
||||
var request = new ChangePasswordRequest
|
||||
{
|
||||
AccessCode = inputAccessCode,
|
||||
OldPassword = inputOldPassword,
|
||||
NewPassword = inputNewPassword
|
||||
};
|
||||
|
||||
|
||||
var responseMessage = await client.PostAsJsonAsync("api/Auth/ChangePassword", request);
|
||||
|
||||
|
||||
if (responseMessage.IsSuccessStatusCode) return 1;
|
||||
|
||||
|
||||
// Unauthorized, extract specific error message as json
|
||||
var responseContent = await responseMessage.Content.ReadAsStringAsync();
|
||||
var responseJson = JsonSerializer.Deserialize<Dictionary<string, string>>(responseContent);
|
||||
@ -182,40 +202,39 @@ public class LoginService
|
||||
public async Task Logout()
|
||||
{
|
||||
IsLoggedIn = false;
|
||||
LoggedInUser = new User();
|
||||
LoggedInBaid = 0;
|
||||
IsAdmin = false;
|
||||
|
||||
|
||||
// Clear JWT token
|
||||
await localStorage.RemoveItemAsync("authToken");
|
||||
OnLoginStatusChanged();
|
||||
}
|
||||
|
||||
public User GetLoggedInUser()
|
||||
public async Task<User?> GetLoggedInUser()
|
||||
{
|
||||
return LoggedInUser;
|
||||
return await client.GetFromJsonAsync<User>($"api/Users/{LoggedInBaid}");
|
||||
}
|
||||
|
||||
public uint GetLoggedInBaid()
|
||||
{
|
||||
return LoggedInBaid;
|
||||
}
|
||||
|
||||
public void ResetLoggedInUser(DashboardResponse? response)
|
||||
{
|
||||
if (response is null) return;
|
||||
var baid = LoggedInUser.Baid;
|
||||
var newLoggedInUser = response.Users.FirstOrDefault(u => u.Baid == baid);
|
||||
if (newLoggedInUser is null) return;
|
||||
LoggedInUser = newLoggedInUser;
|
||||
}
|
||||
|
||||
public async Task<int> BindAccessCode(string inputAccessCode, User user, HttpClient client)
|
||||
public async Task<int> BindAccessCode(string inputAccessCode, User user)
|
||||
{
|
||||
if (inputAccessCode.Trim() == "") return 4; /*Empty access code*/
|
||||
if (!IsLoggedIn && LoginRequired) return 0; /*User not connected and login is required*/
|
||||
if (LoginRequired && !IsAdmin && user.Baid != GetLoggedInUser().Baid) return 5; /*User not admin trying to update someone elses Access Codes*/
|
||||
var loggedInUser = await GetLoggedInUser();
|
||||
if (loggedInUser == null) return 0;
|
||||
if (LoginRequired && !IsAdmin && user.Baid != loggedInUser.Baid) return 5; /*User not admin trying to update someone else's Access Codes*/
|
||||
if (user.AccessCodes.Count >= boundAccessCodeUpperLimit) return 2; /*Limit of codes has been reached*/
|
||||
|
||||
var request = new BindAccessCodeRequest
|
||||
{
|
||||
AccessCode = inputAccessCode,
|
||||
Baid = user.Baid
|
||||
};
|
||||
var responseMessage = await client.PostAsJsonAsync("api/Cards", request);
|
||||
var responseMessage = await client.PostAsJsonAsync("api/Cards/BindAccessCode", request);
|
||||
return responseMessage.IsSuccessStatusCode ? 1 : 3;
|
||||
}
|
||||
}
|
||||
}
|
@ -234,6 +234,11 @@ public class GameDataService : IGameDataService
|
||||
.Select(entry => entry.Rarity)
|
||||
.FirstOrDefault();
|
||||
|
||||
if (!titlePlateIdList.Contains(titleRarity))
|
||||
{
|
||||
titlePlateIdList.Add(titleRarity);
|
||||
}
|
||||
|
||||
set.Add(new Title
|
||||
{
|
||||
TitleName = titleWordlistItem.JapaneseText,
|
||||
|
@ -68,6 +68,12 @@
|
||||
<_ContentIncludedByDefault Remove="Pages\Pages\Profile.razor" />
|
||||
<_ContentIncludedByDefault Remove="Pages\Pages\Register.razor" />
|
||||
<_ContentIncludedByDefault Remove="Pages\Pages\Users.razor" />
|
||||
<_ContentIncludedByDefault Remove="Pages\Pages\Dialogs\OTPDialog.razor" />
|
||||
<_ContentIncludedByDefault Remove="Pages\Pages\Dialogs\ResetPasswordConfirmDialog.razor" />
|
||||
<_ContentIncludedByDefault Remove="Pages\Pages\Login.razor" />
|
||||
<_ContentIncludedByDefault Remove="Pages\Pages\PlayHistory.razor" />
|
||||
<_ContentIncludedByDefault Remove="Pages\Pages\Song.razor" />
|
||||
<_ContentIncludedByDefault Remove="Pages\Pages\SongList.razor" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@ -105,6 +111,15 @@
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<UpToDateCheckInput Remove="Pages\Pages\AccessCode.razor" />
|
||||
<UpToDateCheckInput Remove="Pages\Pages\ChangePassword.razor" />
|
||||
<UpToDateCheckInput Remove="Pages\Pages\DaniDojo.razor" />
|
||||
<UpToDateCheckInput Remove="Pages\Pages\Dashboard.razor" />
|
||||
<UpToDateCheckInput Remove="Pages\Pages\Dialogs\AccessCodeDeleteConfirmDialog.razor" />
|
||||
<UpToDateCheckInput Remove="Pages\Pages\Dialogs\ChooseTitleDialog.razor" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
|
||||
</Project>
|
Loading…
x
Reference in New Issue
Block a user