Stage changes
This commit is contained in:
parent
bc2e30757e
commit
5668aad9bc
@ -10,6 +10,7 @@
|
||||
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
|
||||
<PackageReference Include="MediatR" Version="12.4.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.11" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
|
||||
<PackageReference Include="Otp.NET" Version="1.4.0" />
|
||||
<PackageReference Include="Riok.Mapperly" Version="4.1.1-next.0" />
|
||||
<PackageReference Include="Swan.Core" Version="7.0.0-beta.2" />
|
||||
|
16
Application/DependencyInjection.cs
Normal file
16
Application/DependencyInjection.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using System.Reflection;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Application;
|
||||
|
||||
public static class DependencyInjection
|
||||
{
|
||||
public static IServiceCollection AddApplication(this IServiceCollection services)
|
||||
{
|
||||
services.AddMediatR(
|
||||
configuration => configuration.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly()));
|
||||
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
@ -8,6 +8,8 @@ global using Domain.Common;
|
||||
global using Domain.Entities;
|
||||
global using Domain.Enums;
|
||||
global using Domain.Models;
|
||||
global using Domain.Models.Base;
|
||||
global using Domain.Models.GameData;
|
||||
global using MediatR;
|
||||
global using Microsoft.EntityFrameworkCore;
|
||||
global using Microsoft.Extensions.Logging;
|
||||
|
19
Application/Handlers/Api/User/DeleteUserCommand.cs
Normal file
19
Application/Handlers/Api/User/DeleteUserCommand.cs
Normal file
@ -0,0 +1,19 @@
|
||||
namespace Application.Handlers.Api.User;
|
||||
|
||||
public record DeleteUserCommand(uint Baid) : IRequest<ApiResult<bool>>;
|
||||
|
||||
public class DeleteUserCommandHandler(ITaikoDbContext context) : IRequestHandler<DeleteUserCommand, ApiResult<bool>>
|
||||
{
|
||||
public async Task<ApiResult<bool>> Handle(DeleteUserCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
var userDatum = await context.UserData.FindAsync([request.Baid], cancellationToken);
|
||||
if (userDatum == null)
|
||||
{
|
||||
return ApiResult.Failed<bool>("User not found.");
|
||||
}
|
||||
context.UserData.Remove(userDatum);
|
||||
await context.SaveChangesAsync(cancellationToken);
|
||||
|
||||
return ApiResult.Succeed(true);
|
||||
}
|
||||
}
|
128
Application/Handlers/Api/User/GetSongLeaderboardQuery.cs
Normal file
128
Application/Handlers/Api/User/GetSongLeaderboardQuery.cs
Normal file
@ -0,0 +1,128 @@
|
||||
namespace Application.Handlers.Api.User;
|
||||
|
||||
using LeaderBoard = PaginatedResult<SongLeaderboardEntry>;
|
||||
public record GetSongLeaderboardQuery(uint SongId, Difficulty Difficulty, int Baid, int Page, int Limit) : IRequest<ApiResult<LeaderBoard>>;
|
||||
|
||||
public class GetSongLeaderboardQueryHandler(ITaikoDbContext context, ILogger<GetSongLeaderboardQueryHandler> logger)
|
||||
: IRequestHandler<GetSongLeaderboardQuery, ApiResult<LeaderBoard>>
|
||||
{
|
||||
public async Task<ApiResult<LeaderBoard>> Handle(GetSongLeaderboardQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
var totalScores = await context.SongBestData
|
||||
.Where(x => x.SongId == request.SongId && x.Difficulty == request.Difficulty)
|
||||
.CountAsync(cancellationToken);
|
||||
|
||||
var totalPages = (totalScores + request.Limit - 1) / request.Limit;
|
||||
|
||||
var scores = await context.SongBestData
|
||||
.Where(x => x.SongId == request.SongId && x.Difficulty == request.Difficulty)
|
||||
.Select(x => new
|
||||
{
|
||||
x.Baid,
|
||||
x.BestScore,
|
||||
x.BestRate,
|
||||
x.BestCrown,
|
||||
x.BestScoreRank,
|
||||
Rank = context.SongBestData.Count(y => y.SongId == request.SongId && y.Difficulty == request.Difficulty && y.BestScore > x.BestScore) + 1
|
||||
})
|
||||
.OrderByDescending(x => x.BestScore)
|
||||
.ThenByDescending(x => x.BestRate)
|
||||
.ThenByDescending(x => x.BestCrown)
|
||||
.Skip((request.Page - 1) * request.Limit)
|
||||
.Take(request.Limit)
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
var userIds = scores.Select(x => x.Baid).Distinct().ToList();
|
||||
var users = await context.UserData
|
||||
.Where(x => userIds.Contains(x.Baid))
|
||||
.ToDictionaryAsync(x => x.Baid, cancellationToken);
|
||||
|
||||
var leaderboard = scores.Select(score =>
|
||||
{
|
||||
var user = users.GetValueOrDefault(score.Baid);
|
||||
return new SongLeaderboardEntry
|
||||
{
|
||||
Rank = score.Rank,
|
||||
Baid = score.Baid,
|
||||
UserName = user?.MyDonName,
|
||||
BestScore = score.BestScore,
|
||||
BestRate = score.BestRate,
|
||||
BestCrown = score.BestCrown,
|
||||
BestScoreRank = score.BestScoreRank
|
||||
};
|
||||
}).ToList();
|
||||
|
||||
var userLeaderboardEntry = context.SongBestData
|
||||
.Where(x => x.SongId == request.SongId && x.Difficulty == request.Difficulty && x.Baid == request.Baid)
|
||||
.Select(x => new SongLeaderboardEntry
|
||||
{
|
||||
Baid = x.Baid,
|
||||
BestScore = x.BestScore,
|
||||
BestRate = x.BestRate,
|
||||
BestCrown = x.BestCrown,
|
||||
BestScoreRank = x.BestScoreRank,
|
||||
Rank = context.SongBestData.Count(y => y.SongId == request.SongId && y.Difficulty == request.Difficulty && y.BestScore > x.BestScore) + 1
|
||||
})
|
||||
.FirstOrDefault();
|
||||
/*foreach (var score in scores)
|
||||
{
|
||||
var user = await context.UserData
|
||||
.Where(x => x.Baid == score.Baid)
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
|
||||
var rank = await context.SongBestData
|
||||
.Where(x => x.SongId == request.SongId && x.Difficulty == request.Difficulty && x.BestScore > score.BestScore)
|
||||
.CountAsync(cancellationToken);
|
||||
|
||||
leaderboard.Add(new SongLeaderboardEntry
|
||||
{
|
||||
Rank = rank + 1,
|
||||
Baid = score.Baid,
|
||||
UserName = user?.MyDonName,
|
||||
BestScore = score.BestScore,
|
||||
BestRate = score.BestRate,
|
||||
BestCrown = score.BestCrown,
|
||||
BestScoreRank = score.BestScoreRank
|
||||
});
|
||||
}*/
|
||||
|
||||
/*SongLeaderboardEntry? userBestScore = null;
|
||||
if (request.Baid != 0)
|
||||
{
|
||||
var score = await context.SongBestData
|
||||
.Where(x => x.SongId == request.SongId && x.Difficulty == request.Difficulty && x.Baid == request.Baid)
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
|
||||
if (score != null)
|
||||
{
|
||||
var user = await context.UserData
|
||||
.Where(x => x.Baid == request.Baid)
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
|
||||
var rank = await context.SongBestData
|
||||
.Where(x => x.SongId == request.SongId && x.Difficulty == request.Difficulty && x.BestScore > score.BestScore)
|
||||
.CountAsync(cancellationToken);
|
||||
|
||||
userBestScore = new SongLeaderboardEntry
|
||||
{
|
||||
Rank = rank + 1,
|
||||
Baid = score.Baid,
|
||||
UserName = user?.MyDonName,
|
||||
BestScore = score.BestScore,
|
||||
BestRate = score.BestRate,
|
||||
BestCrown = score.BestCrown,
|
||||
BestScoreRank = score.BestScoreRank
|
||||
};
|
||||
}
|
||||
}*/
|
||||
|
||||
return ApiResult.Succeed(new LeaderBoard
|
||||
{
|
||||
Data = leaderboard,
|
||||
Current = userLeaderboardEntry,
|
||||
CurrentPage = request.Page,
|
||||
TotalPages = totalPages,
|
||||
TotalCount = totalScores
|
||||
});
|
||||
}
|
||||
}
|
25
Application/Handlers/Api/User/GetUserQuery.cs
Normal file
25
Application/Handlers/Api/User/GetUserQuery.cs
Normal file
@ -0,0 +1,25 @@
|
||||
namespace Application.Handlers.Api.User;
|
||||
|
||||
public record GetUserQuery(uint Baid) : IRequest<ApiResult<Domain.Models.User>>;
|
||||
|
||||
public class GetUserQueryHandler(ITaikoDbContext context) : IRequestHandler<GetUserQuery, ApiResult<Domain.Models.User>>
|
||||
{
|
||||
public async Task<ApiResult<Domain.Models.User>> Handle(GetUserQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
var userDatum = await context.UserData.Include(datum => datum.Cards)
|
||||
.Where(datum => datum.Baid == request.Baid)
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
|
||||
if (userDatum == null)
|
||||
{
|
||||
return ApiResult.Failed<Domain.Models.User>("User not found.");
|
||||
}
|
||||
|
||||
return ApiResult.Succeed(new Domain.Models.User
|
||||
{
|
||||
Baid = userDatum.Baid,
|
||||
AccessCodes = userDatum.Cards.Select(card => card.AccessCode).ToList(),
|
||||
IsAdmin = userDatum.IsAdmin
|
||||
});
|
||||
}
|
||||
}
|
58
Application/Handlers/Api/User/GetUsersQuery.cs
Normal file
58
Application/Handlers/Api/User/GetUsersQuery.cs
Normal file
@ -0,0 +1,58 @@
|
||||
using Application.Mappers;
|
||||
|
||||
namespace Application.Handlers.Api.User;
|
||||
|
||||
using Users = PaginatedResult<Domain.Models.User>;
|
||||
|
||||
public record GetUsersQuery(int Page, int Limit, string? SearchTerm) : IRequest<ApiResult<Users>>;
|
||||
|
||||
public class GetUsersQueryHandler(ITaikoDbContext context) : IRequestHandler<GetUsersQuery, ApiResult<Users>>
|
||||
{
|
||||
public async Task<ApiResult<Users>> Handle(GetUsersQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
var users = new List<Domain.Models.User>();
|
||||
|
||||
var cardEntries = await context.Cards.ToListAsync(cancellationToken);
|
||||
var userEntriesQuery = context.UserData.AsQueryable();
|
||||
|
||||
if (!string.IsNullOrEmpty(request.SearchTerm))
|
||||
{
|
||||
var lowerCaseSearchTerm = request.SearchTerm.ToLower();
|
||||
userEntriesQuery = userEntriesQuery.Where(user =>
|
||||
user.Baid.ToString() == lowerCaseSearchTerm ||
|
||||
user.MyDonName.Contains(lowerCaseSearchTerm, StringComparison.CurrentCultureIgnoreCase) ||
|
||||
context.Cards.Any(card => card.Baid == user.Baid &&
|
||||
card.AccessCode.Contains(lowerCaseSearchTerm, StringComparison.CurrentCultureIgnoreCase)));
|
||||
}
|
||||
|
||||
var totalUsers = await userEntriesQuery.CountAsync(cancellationToken);
|
||||
var totalPages = (totalUsers + request.Limit - 1) / request.Limit;
|
||||
|
||||
var userEntries = await userEntriesQuery
|
||||
.OrderBy(user => user.Baid)
|
||||
.Skip((request.Page - 1) * request.Limit)
|
||||
.Take(request.Limit)
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
foreach (var user in userEntries)
|
||||
{
|
||||
var userSetting = UserSettingMapper.MapToUserSetting(user);
|
||||
|
||||
users.Add(new Domain.Models.User
|
||||
{
|
||||
Baid = user.Baid,
|
||||
AccessCodes = cardEntries.Where(card => card.Baid == user.Baid).Select(card => card.AccessCode).ToList(),
|
||||
IsAdmin = user.IsAdmin,
|
||||
UserSetting = userSetting
|
||||
});
|
||||
}
|
||||
|
||||
return ApiResult.Succeed( new Users
|
||||
{
|
||||
Data = users,
|
||||
CurrentPage = request.Page,
|
||||
TotalPages = totalPages,
|
||||
TotalCount = totalUsers
|
||||
});
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
using System.Collections.Immutable;
|
||||
using Domain.Models.GameData;
|
||||
using Domain.Settings;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System.Collections.Immutable;
|
||||
using Domain.Models.GameData;
|
||||
|
||||
namespace Application.Interfaces;
|
||||
|
||||
|
33
Application/Mappers/UserSettingMapper.cs
Normal file
33
Application/Mappers/UserSettingMapper.cs
Normal file
@ -0,0 +1,33 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Riok.Mapperly.Abstractions;
|
||||
|
||||
namespace Application.Mappers;
|
||||
|
||||
[Mapper(AutoUserMappings = false)]
|
||||
public static partial class UserSettingMapper
|
||||
{
|
||||
[SuppressMessage("Mapper", "RMG020:Source member is not mapped to any target member")]
|
||||
[MapProperty(nameof(UserDatum.TitleFlgArray), nameof(UserSetting.UnlockedTitle))]
|
||||
[MapProperty(nameof(UserDatum.OptionSetting), nameof(UserSetting.PlaySetting), Use = nameof(ShortToPlaySetting))]
|
||||
[MapProperty(nameof(UserDatum.UnlockedKigurumi), nameof(UserSetting.UnlockedKigurumi), Use = nameof(FixUnlock))]
|
||||
[MapProperty(nameof(UserDatum.UnlockedBody), nameof(UserSetting.UnlockedBody), Use = nameof(FixUnlock))]
|
||||
[MapProperty(nameof(UserDatum.UnlockedFace), nameof(UserSetting.UnlockedFace), Use = nameof(FixUnlock))]
|
||||
[MapProperty(nameof(UserDatum.UnlockedHead), nameof(UserSetting.UnlockedHead), Use = nameof(FixUnlock))]
|
||||
[MapProperty(nameof(UserDatum.UnlockedPuchi), nameof(UserSetting.UnlockedPuchi), Use = nameof(FixUnlock))]
|
||||
public static partial UserSetting MapToUserSetting(UserDatum user);
|
||||
|
||||
public static PlaySetting ShortToPlaySetting(short option)
|
||||
{
|
||||
return PlaySettingConverter.ShortToPlaySetting(option);
|
||||
}
|
||||
|
||||
public static List<uint> FixUnlock(List<uint> unlock)
|
||||
{
|
||||
if (!unlock.Contains(0))
|
||||
{
|
||||
unlock.Add(0);
|
||||
}
|
||||
|
||||
return unlock;
|
||||
}
|
||||
}
|
@ -1,4 +1,6 @@
|
||||
namespace Application.Models.Game;
|
||||
using Domain.Models.GameData;
|
||||
|
||||
namespace Application.Models.Game;
|
||||
|
||||
public class CommonGetFolderResponse
|
||||
{
|
||||
|
@ -1,4 +1,6 @@
|
||||
namespace Application.Models.Game;
|
||||
using Domain.Models.GameData;
|
||||
|
||||
namespace Application.Models.Game;
|
||||
|
||||
public class CommonGetShopFolderResponse
|
||||
{
|
||||
|
@ -1,4 +1,6 @@
|
||||
namespace Application.Models.Game;
|
||||
using Domain.Models.GameData;
|
||||
|
||||
namespace Application.Models.Game;
|
||||
|
||||
public class CommonGetSongIntroductionResponse
|
||||
{
|
||||
|
43
Application/Utils/PlaySettingConverter.cs
Normal file
43
Application/Utils/PlaySettingConverter.cs
Normal file
@ -0,0 +1,43 @@
|
||||
using System.Collections.Specialized;
|
||||
|
||||
namespace Application.Utils;
|
||||
|
||||
public static class PlaySettingConverter
|
||||
{
|
||||
public static PlaySetting ShortToPlaySetting(short input)
|
||||
{
|
||||
var bits = new BitVector32(input);
|
||||
var speedSection = BitVector32.CreateSection(15);
|
||||
var vanishSection = BitVector32.CreateSection(1, speedSection);
|
||||
var inverseSection = BitVector32.CreateSection(1, vanishSection);
|
||||
var randomSection = BitVector32.CreateSection(2, inverseSection);
|
||||
|
||||
var randomType = (RandomType)bits[randomSection];
|
||||
randomType.Throw().IfOutOfRange();
|
||||
var result = new PlaySetting
|
||||
{
|
||||
Speed = (uint)bits[speedSection],
|
||||
IsVanishOn = bits[vanishSection] == 1,
|
||||
IsInverseOn = bits[inverseSection] == 1,
|
||||
RandomType = randomType
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static short PlaySettingToShort(PlaySetting setting)
|
||||
{
|
||||
var bits = new BitVector32();
|
||||
var speedSection = BitVector32.CreateSection(15);
|
||||
var vanishSection = BitVector32.CreateSection(1, speedSection);
|
||||
var inverseSection = BitVector32.CreateSection(1, vanishSection);
|
||||
var randomSection = BitVector32.CreateSection(2, inverseSection);
|
||||
|
||||
bits[speedSection] = (int)setting.Speed;
|
||||
bits[vanishSection] = setting.IsVanishOn ? 1 : 0;
|
||||
bits[inverseSection] = setting.IsInverseOn ? 1 : 0;
|
||||
bits[randomSection] = (int)setting.RandomType;
|
||||
|
||||
return (short)bits.Data;
|
||||
}
|
||||
}
|
11
Domain/Models/Base/PaginatedResult.cs
Normal file
11
Domain/Models/Base/PaginatedResult.cs
Normal file
@ -0,0 +1,11 @@
|
||||
namespace Domain.Models.Base;
|
||||
|
||||
public class PaginatedResult<T>
|
||||
{
|
||||
public List<T> Data { get; set; } = [];
|
||||
public T? Current;
|
||||
|
||||
public int CurrentPage { get; set; }
|
||||
public int TotalPages { get; set; }
|
||||
public int TotalCount { get; set; }
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Domain.Models.GameData;
|
||||
|
||||
namespace Domain.Models;
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
namespace Domain.Models;
|
||||
namespace Domain.Models.GameData;
|
||||
|
||||
public class Costume
|
||||
{
|
@ -1,6 +1,6 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Domain.Models;
|
||||
namespace Domain.Models.GameData;
|
||||
|
||||
public class DonCosRewardEntry
|
||||
{
|
@ -1,6 +1,6 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Domain.Models;
|
||||
namespace Domain.Models.GameData;
|
||||
|
||||
public class DonCosRewards
|
||||
{
|
@ -1,6 +1,6 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Domain.Models;
|
||||
namespace Domain.Models.GameData;
|
||||
|
||||
public class EventFolderData : IVerupNo
|
||||
{
|
@ -1,4 +1,4 @@
|
||||
namespace Domain.Models;
|
||||
namespace Domain.Models.GameData;
|
||||
|
||||
public interface IVerupNo
|
||||
{
|
@ -1,6 +1,6 @@
|
||||
using Domain.Enums;
|
||||
|
||||
namespace Domain.Models;
|
||||
namespace Domain.Models.GameData;
|
||||
|
||||
public class MusicDetail
|
||||
{
|
@ -1,7 +1,7 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Domain.Enums;
|
||||
|
||||
namespace Domain.Models;
|
||||
namespace Domain.Models.GameData;
|
||||
|
||||
public class MusicInfoEntry
|
||||
{
|
@ -1,6 +1,6 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Domain.Models;
|
||||
namespace Domain.Models.GameData;
|
||||
|
||||
public class MusicInfos
|
||||
{
|
@ -1,6 +1,6 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Domain.Models;
|
||||
namespace Domain.Models.GameData;
|
||||
|
||||
public class MusicOrder
|
||||
{
|
@ -1,6 +1,6 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Domain.Models;
|
||||
namespace Domain.Models.GameData;
|
||||
|
||||
public class MusicOrderEntry
|
||||
{
|
@ -1,6 +1,6 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Domain.Models;
|
||||
namespace Domain.Models.GameData;
|
||||
|
||||
public class NeiroEntry
|
||||
{
|
@ -1,6 +1,6 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Domain.Models;
|
||||
namespace Domain.Models.GameData;
|
||||
|
||||
public class Neiros
|
||||
{
|
@ -1,6 +1,6 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Domain.Models;
|
||||
namespace Domain.Models.GameData;
|
||||
|
||||
public class QRCodeData
|
||||
{
|
@ -1,6 +1,6 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Domain.Models;
|
||||
namespace Domain.Models.GameData;
|
||||
|
||||
public class ShopFolderData
|
||||
{
|
@ -1,6 +1,6 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Domain.Models;
|
||||
namespace Domain.Models.GameData;
|
||||
|
||||
public class ShougouEntry
|
||||
{
|
@ -1,6 +1,6 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Domain.Models;
|
||||
namespace Domain.Models.GameData;
|
||||
|
||||
public class Shougous
|
||||
{
|
@ -1,6 +1,6 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Domain.Models;
|
||||
namespace Domain.Models.GameData;
|
||||
|
||||
public class SongIntroductionData : IVerupNo
|
||||
{
|
@ -1,6 +1,6 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Domain.Models;
|
||||
namespace Domain.Models.GameData;
|
||||
|
||||
public class WordList
|
||||
{
|
@ -1,6 +1,6 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Domain.Models;
|
||||
namespace Domain.Models.GameData;
|
||||
|
||||
public class WordListEntry
|
||||
{
|
@ -1,27 +1,10 @@
|
||||
using Domain.Enums;
|
||||
|
||||
namespace Domain.Models;
|
||||
namespace Domain.Models;
|
||||
|
||||
public class SongLeaderboard
|
||||
{
|
||||
public int Rank { get; set; }
|
||||
|
||||
public uint Baid { get; set; }
|
||||
|
||||
public uint BestScore { get; set; }
|
||||
|
||||
public uint BestRate { get; set; }
|
||||
|
||||
public CrownType BestCrown { get; set; }
|
||||
|
||||
public ScoreRank BestScoreRank { get; set; }
|
||||
|
||||
public uint GoodCount { get; set; }
|
||||
public uint OkCount { get; set; }
|
||||
public uint MissCount { get; set; }
|
||||
public uint ComboCount { get; set; }
|
||||
public uint HitCount { get; set; }
|
||||
public uint DrumrollCount { get; set; }
|
||||
|
||||
public string? UserName { get; set; }
|
||||
public List<SongLeaderboardEntry> LeaderboardData { get; set; } = [];
|
||||
public SongLeaderboardEntry? UserScore { get; set; }
|
||||
public int CurrentPage { get; set; }
|
||||
public int TotalPages { get; set; }
|
||||
public int TotalScores { get; set; }
|
||||
}
|
27
Domain/Models/SongLeaderboardEntry.cs
Normal file
27
Domain/Models/SongLeaderboardEntry.cs
Normal file
@ -0,0 +1,27 @@
|
||||
using Domain.Enums;
|
||||
|
||||
namespace Domain.Models;
|
||||
|
||||
public class SongLeaderboardEntry
|
||||
{
|
||||
public int Rank { get; set; }
|
||||
|
||||
public uint Baid { get; set; }
|
||||
|
||||
public uint BestScore { get; set; }
|
||||
|
||||
public uint BestRate { get; set; }
|
||||
|
||||
public CrownType BestCrown { get; set; }
|
||||
|
||||
public ScoreRank BestScoreRank { get; set; }
|
||||
|
||||
public uint GoodCount { get; set; }
|
||||
public uint OkCount { get; set; }
|
||||
public uint MissCount { get; set; }
|
||||
public uint ComboCount { get; set; }
|
||||
public uint HitCount { get; set; }
|
||||
public uint DrumrollCount { get; set; }
|
||||
|
||||
public string? UserName { get; set; }
|
||||
}
|
@ -64,5 +64,5 @@ public class UserSetting
|
||||
|
||||
public uint ColorLimb { get; set; }
|
||||
|
||||
public DateTime LastPlayDateTime { get; set; }
|
||||
public DateTime LastPlayDatetime { get; set; }
|
||||
}
|
34
Infrastructure/DependencyInjection.cs
Normal file
34
Infrastructure/DependencyInjection.cs
Normal file
@ -0,0 +1,34 @@
|
||||
using Application.Interfaces;
|
||||
using Domain.Common;
|
||||
using Infrastructure.Persistence;
|
||||
using Infrastructure.Services;
|
||||
using Infrastructure.Utils;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Infrastructure;
|
||||
|
||||
public static class DependencyInjection
|
||||
{
|
||||
public static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
services.AddSingleton<IJwtTokenService, JwtTokenService>();
|
||||
services.AddSingleton<IGameDataService, GameDataService>();
|
||||
services.AddDbContext<TaikoDbContext>(option =>
|
||||
{
|
||||
var dbName = configuration["DbFileName"];
|
||||
if (string.IsNullOrEmpty(dbName))
|
||||
{
|
||||
dbName = Constants.DefaultDbName;
|
||||
}
|
||||
|
||||
var path = Path.Combine(PathHelper.GetRootPath(), dbName);
|
||||
option.UseSqlite($"Data Source={path}");
|
||||
});
|
||||
services.AddScoped<ITaikoDbContext, TaikoDbContext>(provider =>
|
||||
provider.GetService<TaikoDbContext>() ?? throw new InvalidOperationException());
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
@ -15,6 +15,8 @@
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="8.2.0" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.2.0" />
|
||||
<PackageReference Include="Throw" Version="1.4.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
@ -5,6 +5,7 @@ using System.Text.Json;
|
||||
using Application.Interfaces;
|
||||
using Domain.Common;
|
||||
using Domain.Models;
|
||||
using Domain.Models.GameData;
|
||||
using Domain.Settings;
|
||||
using Infrastructure.Utils;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
35
Infrastructure/Services/JwtTokenService.cs
Normal file
35
Infrastructure/Services/JwtTokenService.cs
Normal file
@ -0,0 +1,35 @@
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
using Application.Interfaces;
|
||||
using Domain.Settings;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
|
||||
namespace Infrastructure.Services;
|
||||
|
||||
public class JwtTokenService(IOptions<AuthSettings> options) : IJwtTokenService
|
||||
{
|
||||
public string GenerateToken(uint baid, bool isAdmin)
|
||||
{
|
||||
var authSettings = options.Value;
|
||||
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(authSettings.JwtKey));
|
||||
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
|
||||
|
||||
var claims = new List<Claim>
|
||||
{
|
||||
new(ClaimTypes.Name, baid.ToString()),
|
||||
new(ClaimTypes.Role, isAdmin ? "Admin" : "User")
|
||||
};
|
||||
|
||||
var token = new JwtSecurityToken(
|
||||
issuer: authSettings.JwtIssuer,
|
||||
audience: authSettings.JwtAudience,
|
||||
expires: DateTime.UtcNow.AddHours(24),
|
||||
signingCredentials: credentials,
|
||||
claims: claims
|
||||
);
|
||||
|
||||
return new JwtSecurityTokenHandler().WriteToken(token);
|
||||
}
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Server.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("[controller]")]
|
||||
public class WeatherForecastController : ControllerBase
|
||||
{
|
||||
private static readonly string[] Summaries = new[]
|
||||
{
|
||||
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
|
||||
};
|
||||
|
||||
private readonly ILogger<WeatherForecastController> _logger;
|
||||
|
||||
public WeatherForecastController(ILogger<WeatherForecastController> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
[HttpGet(Name = "GetWeatherForecast")]
|
||||
public IEnumerable<WeatherForecast> Get()
|
||||
{
|
||||
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
|
||||
{
|
||||
Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
|
||||
TemperatureC = Random.Shared.Next(-20, 55),
|
||||
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
|
||||
})
|
||||
.ToArray();
|
||||
}
|
||||
}
|
@ -10,4 +10,8 @@
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Controllers\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@ -1,6 +0,0 @@
|
||||
@Server_HostAddress = http://localhost:5247
|
||||
|
||||
GET {{Server_HostAddress}}/weatherforecast/
|
||||
Accept: application/json
|
||||
|
||||
###
|
@ -1,12 +0,0 @@
|
||||
namespace Server;
|
||||
|
||||
public class WeatherForecast
|
||||
{
|
||||
public DateOnly Date { get; set; }
|
||||
|
||||
public int TemperatureC { get; set; }
|
||||
|
||||
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
|
||||
|
||||
public string? Summary { get; set; }
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user