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="BCrypt.Net-Next" Version="4.0.3" />
|
||||||
<PackageReference Include="MediatR" Version="12.4.1" />
|
<PackageReference Include="MediatR" Version="12.4.1" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.11" />
|
<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="Otp.NET" Version="1.4.0" />
|
||||||
<PackageReference Include="Riok.Mapperly" Version="4.1.1-next.0" />
|
<PackageReference Include="Riok.Mapperly" Version="4.1.1-next.0" />
|
||||||
<PackageReference Include="Swan.Core" Version="7.0.0-beta.2" />
|
<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.Entities;
|
||||||
global using Domain.Enums;
|
global using Domain.Enums;
|
||||||
global using Domain.Models;
|
global using Domain.Models;
|
||||||
|
global using Domain.Models.Base;
|
||||||
|
global using Domain.Models.GameData;
|
||||||
global using MediatR;
|
global using MediatR;
|
||||||
global using Microsoft.EntityFrameworkCore;
|
global using Microsoft.EntityFrameworkCore;
|
||||||
global using Microsoft.Extensions.Logging;
|
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 System.Collections.Immutable;
|
||||||
|
using Domain.Models.GameData;
|
||||||
using Domain.Settings;
|
using Domain.Settings;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
|
using Domain.Models.GameData;
|
||||||
|
|
||||||
namespace Application.Interfaces;
|
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
|
public class CommonGetFolderResponse
|
||||||
{
|
{
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
namespace Application.Models.Game;
|
using Domain.Models.GameData;
|
||||||
|
|
||||||
|
namespace Application.Models.Game;
|
||||||
|
|
||||||
public class CommonGetShopFolderResponse
|
public class CommonGetShopFolderResponse
|
||||||
{
|
{
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
namespace Application.Models.Game;
|
using Domain.Models.GameData;
|
||||||
|
|
||||||
|
namespace Application.Models.Game;
|
||||||
|
|
||||||
public class CommonGetSongIntroductionResponse
|
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 System.Text.Json.Serialization;
|
||||||
|
using Domain.Models.GameData;
|
||||||
|
|
||||||
namespace Domain.Models;
|
namespace Domain.Models;
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
namespace Domain.Models;
|
namespace Domain.Models.GameData;
|
||||||
|
|
||||||
public class Costume
|
public class Costume
|
||||||
{
|
{
|
@ -1,6 +1,6 @@
|
|||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace Domain.Models;
|
namespace Domain.Models.GameData;
|
||||||
|
|
||||||
public class DonCosRewardEntry
|
public class DonCosRewardEntry
|
||||||
{
|
{
|
@ -1,6 +1,6 @@
|
|||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace Domain.Models;
|
namespace Domain.Models.GameData;
|
||||||
|
|
||||||
public class DonCosRewards
|
public class DonCosRewards
|
||||||
{
|
{
|
@ -1,6 +1,6 @@
|
|||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace Domain.Models;
|
namespace Domain.Models.GameData;
|
||||||
|
|
||||||
public class EventFolderData : IVerupNo
|
public class EventFolderData : IVerupNo
|
||||||
{
|
{
|
@ -1,4 +1,4 @@
|
|||||||
namespace Domain.Models;
|
namespace Domain.Models.GameData;
|
||||||
|
|
||||||
public interface IVerupNo
|
public interface IVerupNo
|
||||||
{
|
{
|
@ -1,6 +1,6 @@
|
|||||||
using Domain.Enums;
|
using Domain.Enums;
|
||||||
|
|
||||||
namespace Domain.Models;
|
namespace Domain.Models.GameData;
|
||||||
|
|
||||||
public class MusicDetail
|
public class MusicDetail
|
||||||
{
|
{
|
@ -1,7 +1,7 @@
|
|||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
using Domain.Enums;
|
using Domain.Enums;
|
||||||
|
|
||||||
namespace Domain.Models;
|
namespace Domain.Models.GameData;
|
||||||
|
|
||||||
public class MusicInfoEntry
|
public class MusicInfoEntry
|
||||||
{
|
{
|
@ -1,6 +1,6 @@
|
|||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace Domain.Models;
|
namespace Domain.Models.GameData;
|
||||||
|
|
||||||
public class MusicInfos
|
public class MusicInfos
|
||||||
{
|
{
|
@ -1,6 +1,6 @@
|
|||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace Domain.Models;
|
namespace Domain.Models.GameData;
|
||||||
|
|
||||||
public class MusicOrder
|
public class MusicOrder
|
||||||
{
|
{
|
@ -1,6 +1,6 @@
|
|||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace Domain.Models;
|
namespace Domain.Models.GameData;
|
||||||
|
|
||||||
public class MusicOrderEntry
|
public class MusicOrderEntry
|
||||||
{
|
{
|
@ -1,6 +1,6 @@
|
|||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace Domain.Models;
|
namespace Domain.Models.GameData;
|
||||||
|
|
||||||
public class NeiroEntry
|
public class NeiroEntry
|
||||||
{
|
{
|
@ -1,6 +1,6 @@
|
|||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace Domain.Models;
|
namespace Domain.Models.GameData;
|
||||||
|
|
||||||
public class Neiros
|
public class Neiros
|
||||||
{
|
{
|
@ -1,6 +1,6 @@
|
|||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace Domain.Models;
|
namespace Domain.Models.GameData;
|
||||||
|
|
||||||
public class QRCodeData
|
public class QRCodeData
|
||||||
{
|
{
|
@ -1,6 +1,6 @@
|
|||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace Domain.Models;
|
namespace Domain.Models.GameData;
|
||||||
|
|
||||||
public class ShopFolderData
|
public class ShopFolderData
|
||||||
{
|
{
|
@ -1,6 +1,6 @@
|
|||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace Domain.Models;
|
namespace Domain.Models.GameData;
|
||||||
|
|
||||||
public class ShougouEntry
|
public class ShougouEntry
|
||||||
{
|
{
|
@ -1,6 +1,6 @@
|
|||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace Domain.Models;
|
namespace Domain.Models.GameData;
|
||||||
|
|
||||||
public class Shougous
|
public class Shougous
|
||||||
{
|
{
|
@ -1,6 +1,6 @@
|
|||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace Domain.Models;
|
namespace Domain.Models.GameData;
|
||||||
|
|
||||||
public class SongIntroductionData : IVerupNo
|
public class SongIntroductionData : IVerupNo
|
||||||
{
|
{
|
@ -1,6 +1,6 @@
|
|||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace Domain.Models;
|
namespace Domain.Models.GameData;
|
||||||
|
|
||||||
public class WordList
|
public class WordList
|
||||||
{
|
{
|
@ -1,6 +1,6 @@
|
|||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace Domain.Models;
|
namespace Domain.Models.GameData;
|
||||||
|
|
||||||
public class WordListEntry
|
public class WordListEntry
|
||||||
{
|
{
|
@ -1,27 +1,10 @@
|
|||||||
using Domain.Enums;
|
namespace Domain.Models;
|
||||||
|
|
||||||
namespace Domain.Models;
|
|
||||||
|
|
||||||
public class SongLeaderboard
|
public class SongLeaderboard
|
||||||
{
|
{
|
||||||
public int Rank { get; set; }
|
public List<SongLeaderboardEntry> LeaderboardData { get; set; } = [];
|
||||||
|
public SongLeaderboardEntry? UserScore { get; set; }
|
||||||
public uint Baid { get; set; }
|
public int CurrentPage { get; set; }
|
||||||
|
public int TotalPages { get; set; }
|
||||||
public uint BestScore { get; set; }
|
public int TotalScores { 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; }
|
|
||||||
}
|
}
|
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 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>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</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" />
|
<PackageReference Include="Throw" Version="1.4.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ using System.Text.Json;
|
|||||||
using Application.Interfaces;
|
using Application.Interfaces;
|
||||||
using Domain.Common;
|
using Domain.Common;
|
||||||
using Domain.Models;
|
using Domain.Models;
|
||||||
|
using Domain.Models.GameData;
|
||||||
using Domain.Settings;
|
using Domain.Settings;
|
||||||
using Infrastructure.Utils;
|
using Infrastructure.Utils;
|
||||||
using Microsoft.Extensions.Options;
|
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"/>
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="Controllers\" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</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