diff --git a/Directory.Packages.props b/Directory.Packages.props index e0956de..cadcccc 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -3,9 +3,10 @@ true - + + @@ -20,11 +21,11 @@ - + - - + + diff --git a/src/Open5ETools.Core/ConfigureServices.cs b/src/Open5ETools.Core/ConfigureServices.cs index 2a4dfba..16bfe69 100644 --- a/src/Open5ETools.Core/ConfigureServices.cs +++ b/src/Open5ETools.Core/ConfigureServices.cs @@ -1,12 +1,15 @@ -using Microsoft.Extensions.DependencyInjection; +using Mapster; +using Microsoft.Extensions.DependencyInjection; +using Open5ETools.Core.Common.Enums.SM; using Open5ETools.Core.Common.Helpers; using Open5ETools.Core.Common.Interfaces.Services; using Open5ETools.Core.Common.Interfaces.Services.DM; using Open5ETools.Core.Common.Interfaces.Services.DM.Generator; using Open5ETools.Core.Common.Interfaces.Services.EG; using Open5ETools.Core.Common.Interfaces.Services.SM; +using Open5ETools.Core.Common.Models.EG; +using Open5ETools.Core.Common.Models.Json; using Open5ETools.Core.Services; -using Open5ETools.Core.Services.Automapper; using Open5ETools.Core.Services.DM; using Open5ETools.Core.Services.DM.Generator; using Open5ETools.Core.Services.EG; @@ -31,7 +34,33 @@ this IServiceCollection services .AddScoped() .AddScoped(); - services.AddAutoMapper(cfg => { cfg.AllowNullCollections = true; }, typeof(UserProfile)); + services.ConfigureMapster(); + return services; + } + + + private static IServiceCollection ConfigureMapster(this IServiceCollection services) + { + services.AddMapster(); + TypeAdapterConfig + .NewConfig() + .Map(dest => dest.Concentration, + src => string.IsNullOrWhiteSpace(src.Concentration) || + !src.Concentration.Equals("no", StringComparison.InvariantCultureIgnoreCase)) + .Map(dest => dest.Ritual, + src => string.IsNullOrWhiteSpace(src.Ritual) || + !src.Ritual.Equals("no", StringComparison.InvariantCultureIgnoreCase)) + .Map(dest => dest.School, + src => Enum.Parse(src.School ?? string.Empty)); + + TypeAdapterConfig + .NewConfig() + .Map(dest => dest.Hp, src => src.HitPoints) + .Map(dest => dest.Ac, src => src.ArmorClass); + + TypeAdapterConfig + .NewConfig() + .Map(dest => dest.JsonMonsterModel, src => src.JsonMonster); return services; } diff --git a/src/Open5ETools.Core/Open5ETools.Core.csproj b/src/Open5ETools.Core/Open5ETools.Core.csproj index cb84815..e8b6e4b 100644 --- a/src/Open5ETools.Core/Open5ETools.Core.csproj +++ b/src/Open5ETools.Core/Open5ETools.Core.csproj @@ -1,6 +1,7 @@  - + + diff --git a/src/Open5ETools.Core/Services/AuthService.cs b/src/Open5ETools.Core/Services/AuthService.cs index 1e36ca9..53504b6 100644 --- a/src/Open5ETools.Core/Services/AuthService.cs +++ b/src/Open5ETools.Core/Services/AuthService.cs @@ -1,4 +1,4 @@ -using AutoMapper; +using MapsterMapper; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using Open5ETools.Core.Common.Helpers; @@ -19,9 +19,9 @@ public class AuthService(IMapper mapper, IAppDbContext context, ILogger u.IsDeleted == false) - .FirstOrDefaultAsync(u => u.Username == model.Username, cancellationToken); + .AsNoTracking() + .Where(u => u.IsDeleted == false) + .FirstOrDefaultAsync(u => u.Username == model.Username, cancellationToken); if (user is null) return null; diff --git a/src/Open5ETools.Core/Services/Automapper/DM/DungeonProfile.cs b/src/Open5ETools.Core/Services/Automapper/DM/DungeonProfile.cs deleted file mode 100644 index 478640f..0000000 --- a/src/Open5ETools.Core/Services/Automapper/DM/DungeonProfile.cs +++ /dev/null @@ -1,14 +0,0 @@ -using AutoMapper; -using Open5ETools.Core.Common.Models.DM.Services; -using Open5ETools.Core.Domain.DM; - -namespace Open5ETools.Core.Services.Automapper.DM; - -public class DungeonProfile : Profile -{ - public DungeonProfile() - { - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - } -} \ No newline at end of file diff --git a/src/Open5ETools.Core/Services/Automapper/DM/OptionProfile.cs b/src/Open5ETools.Core/Services/Automapper/DM/OptionProfile.cs deleted file mode 100644 index edbbcc0..0000000 --- a/src/Open5ETools.Core/Services/Automapper/DM/OptionProfile.cs +++ /dev/null @@ -1,13 +0,0 @@ -using AutoMapper; -using Open5ETools.Core.Common.Models.DM.Services; -using Open5ETools.Core.Domain.DM; - -namespace Open5ETools.Core.Services.Automapper.DM; - -public class OptionProfile : Profile -{ - public OptionProfile() - { - CreateMap().ReverseMap(); - } -} \ No newline at end of file diff --git a/src/Open5ETools.Core/Services/Automapper/EG/EncounterProfile.cs b/src/Open5ETools.Core/Services/Automapper/EG/EncounterProfile.cs deleted file mode 100644 index f25d3f1..0000000 --- a/src/Open5ETools.Core/Services/Automapper/EG/EncounterProfile.cs +++ /dev/null @@ -1,15 +0,0 @@ -using AutoMapper; -using Open5ETools.Core.Common.Models.EG; - -namespace Open5ETools.Core.Services.Automapper.EG; - -public class EncounterProfile : Profile -{ - public EncounterProfile() - { - CreateMap(MemberList.None) - .ForMember(d => d.Hp, opt => opt.MapFrom(s => s.HitPoints)) - .ForMember(d => d.Ac, opt => opt.MapFrom(s => s.ArmorClass)); - CreateMap(MemberList.None).ReverseMap(); - } -} \ No newline at end of file diff --git a/src/Open5ETools.Core/Services/Automapper/EG/MonsterProfile.cs b/src/Open5ETools.Core/Services/Automapper/EG/MonsterProfile.cs deleted file mode 100644 index c2070e7..0000000 --- a/src/Open5ETools.Core/Services/Automapper/EG/MonsterProfile.cs +++ /dev/null @@ -1,17 +0,0 @@ -using AutoMapper; -using Open5ETools.Core.Common.Models.EG; -using Open5ETools.Core.Domain.EG; - -namespace Open5ETools.Core.Services.Automapper.EG; - -public class MonsterProfile : Profile -{ - public MonsterProfile() - { - CreateMap(MemberList.None) - .ForMember(d => d.Hp, opt => opt.MapFrom(s => s.HitPoints)) - .ForMember(d => d.Ac, opt => opt.MapFrom(s => s.ArmorClass)); - CreateMap() - .ForMember(d => d.JsonMonsterModel, opt => opt.MapFrom(s => s.JsonMonster)); - } -} \ No newline at end of file diff --git a/src/Open5ETools.Core/Services/Automapper/SM/SpellProfile.cs b/src/Open5ETools.Core/Services/Automapper/SM/SpellProfile.cs deleted file mode 100644 index 9db22ff..0000000 --- a/src/Open5ETools.Core/Services/Automapper/SM/SpellProfile.cs +++ /dev/null @@ -1,20 +0,0 @@ -using AutoMapper; -using Open5ETools.Core.Common.Enums.SM; -using Open5ETools.Core.Common.Models.SM; -using Open5ETools.Core.Domain.SM; - -namespace Open5ETools.Core.Services.Automapper.SM; - -public class SpellProfile : Profile -{ - public SpellProfile() - { - CreateMap(MemberList.None) - .ForMember(d => d.Concentration, opt => opt.MapFrom(s => string.IsNullOrWhiteSpace(s.Concentration) - || !s.Concentration.Equals("no", StringComparison.InvariantCultureIgnoreCase))) - .ForMember(d => d.Ritual, opt => opt.MapFrom(s => string.IsNullOrWhiteSpace(s.Ritual) - || !s.Ritual.Equals("no", StringComparison.InvariantCultureIgnoreCase))) - .ForMember(d => d.School, opt => opt.MapFrom(s => Enum.Parse(s.School ?? string.Empty))); - CreateMap(MemberList.None).ReverseMap(); - } -} \ No newline at end of file diff --git a/src/Open5ETools.Core/Services/Automapper/UserProfile.cs b/src/Open5ETools.Core/Services/Automapper/UserProfile.cs deleted file mode 100644 index 6718893..0000000 --- a/src/Open5ETools.Core/Services/Automapper/UserProfile.cs +++ /dev/null @@ -1,13 +0,0 @@ -using AutoMapper; -using Open5ETools.Core.Common.Models.Services; -using Open5ETools.Core.Domain; - -namespace Open5ETools.Core.Services.Automapper; - -public class UserProfile : Profile -{ - public UserProfile() - { - CreateMap().ReverseMap(); - } -} \ No newline at end of file diff --git a/src/Open5ETools.Core/Services/DM/DungeonService.cs b/src/Open5ETools.Core/Services/DM/DungeonService.cs index a3601fe..b5b85fe 100644 --- a/src/Open5ETools.Core/Services/DM/DungeonService.cs +++ b/src/Open5ETools.Core/Services/DM/DungeonService.cs @@ -1,5 +1,5 @@ using System.Text.Json; -using AutoMapper; +using MapsterMapper; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using Open5ETools.Core.Common.Exceptions; @@ -218,11 +218,12 @@ public async Task> GetAllDungeonOptionsForUserAs { try { - return _mapper.Map(await _context.DungeonOptions + var dungeonOption = await _context.DungeonOptions .Include(d => d.Dungeons) .AsNoTracking() .Where(d => d.DungeonName.Equals(dungeonName) && d.UserId == userId) - .FirstOrDefaultAsync(cancellationToken)); + .FirstOrDefaultAsync(cancellationToken); + return dungeonOption is not null ? _mapper.Map(dungeonOption) : null; } catch (Exception ex) { @@ -341,8 +342,9 @@ public async Task GetDungeonAsync(int id, CancellationToken cancel try { return _mapper.Map(await _context.Dungeons - .AsNoTracking() - .FirstOrDefaultAsync(d => d.Id == id, cancellationToken)); + .AsNoTracking() + .FirstOrDefaultAsync(d => d.Id == id, cancellationToken) ?? + throw new ServiceException(Error.NotFound)); } catch (Exception ex) { @@ -375,7 +377,8 @@ public async Task GetDungeonOptionAsync(int id, Cancellation try { return _mapper.Map( - await _context.DungeonOptions.FirstOrDefaultAsync(d => d.Id == id, cancellationToken)); + await _context.DungeonOptions.FirstOrDefaultAsync(d => d.Id == id, cancellationToken) ?? + throw new ServiceException(Error.NotFound)); } catch (Exception ex) { @@ -408,10 +411,12 @@ public async Task ExportToJsonAsync(int dungeonId, CancellationToken can try { var dungeon = _mapper.Map(await _context.Dungeons - .AsNoTracking() - .FirstOrDefaultAsync(d => d.Id == dungeonId, cancellationToken)); + .AsNoTracking() + .FirstOrDefaultAsync(d => d.Id == dungeonId, + cancellationToken) ?? + throw new ServiceException(Error.NotFound)); - return dungeon is not null ? JsonSerializer.Serialize(dungeon) : string.Empty; + return JsonSerializer.Serialize(dungeon); } catch (Exception ex) { diff --git a/src/Open5ETools.Core/Services/DM/OptionService.cs b/src/Open5ETools.Core/Services/DM/OptionService.cs index 40d0073..52ae052 100644 --- a/src/Open5ETools.Core/Services/DM/OptionService.cs +++ b/src/Open5ETools.Core/Services/DM/OptionService.cs @@ -1,4 +1,4 @@ -using AutoMapper; +using MapsterMapper; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Logging; diff --git a/src/Open5ETools.Core/Services/EG/EncounterService.cs b/src/Open5ETools.Core/Services/EG/EncounterService.cs index d4944c5..2f2f373 100644 --- a/src/Open5ETools.Core/Services/EG/EncounterService.cs +++ b/src/Open5ETools.Core/Services/EG/EncounterService.cs @@ -1,4 +1,4 @@ -using AutoMapper; +using MapsterMapper; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using Open5ETools.Core.Common; @@ -10,6 +10,8 @@ using Open5ETools.Core.Common.Interfaces.Services.EG; using Open5ETools.Core.Common.Models.EG; using Open5ETools.Core.Domain.EG; +using Open5ETools.Resources; +using Enum = System.Enum; namespace Open5ETools.Core.Services.EG; @@ -202,8 +204,9 @@ private MonsterModel GetEncounterDetail(Difficulty difficulty, Monster currentMo public async Task GetMonsterByIdAsync(int id) { var monster = await _context.Monsters - .AsNoTracking() - .FirstOrDefaultAsync(m => m.Id == id); + .AsNoTracking() + .FirstOrDefaultAsync(m => m.Id == id) ?? + throw new ServiceException(Error.NotFound); return _mapper.Map(monster); } diff --git a/src/Open5ETools.Core/Services/SM/SpellService.cs b/src/Open5ETools.Core/Services/SM/SpellService.cs index 131554c..95414bb 100644 --- a/src/Open5ETools.Core/Services/SM/SpellService.cs +++ b/src/Open5ETools.Core/Services/SM/SpellService.cs @@ -1,9 +1,11 @@ -using AutoMapper; +using MapsterMapper; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; +using Open5ETools.Core.Common.Exceptions; using Open5ETools.Core.Common.Interfaces.Data; using Open5ETools.Core.Common.Interfaces.Services.SM; using Open5ETools.Core.Common.Models.SM; +using Open5ETools.Resources; namespace Open5ETools.Core.Services.SM; @@ -11,7 +13,7 @@ public class SpellService( IMapper mapper, IAppDbContext context, ILogger logger - ) : ISpellService +) : ISpellService { private readonly IMapper _mapper = mapper; private readonly IAppDbContext _context = context; @@ -22,8 +24,9 @@ public async Task GetAsync(int id, CancellationToken cancellationTok try { var spell = await _context.Spells - .AsNoTracking() - .FirstOrDefaultAsync(u => u.Id == id, cancellationToken); + .AsNoTracking() + .FirstOrDefaultAsync(u => u.Id == id, cancellationToken) ?? + throw new ServiceException(Error.NotFound); return _mapper.Map(spell); } @@ -34,7 +37,8 @@ public async Task GetAsync(int id, CancellationToken cancellationTok } } - public async Task> ListAsync(string? search = null, CancellationToken cancellationToken = default) + public async Task> ListAsync(string? search = null, + CancellationToken cancellationToken = default) { try { @@ -42,11 +46,11 @@ public async Task> ListAsync(string? search = null, Canc if (!string.IsNullOrWhiteSpace(search)) query = query.Where(s => EF.Functions.Like(s.Name, $"%{search}%") - || EF.Functions.Like(s.Class, $"%{search}%") - || EF.Functions.Like(s.Level, $"%{search}%") - || EF.Functions.Like(s.CastingTime, $"%{search}%") - || EF.Functions.Like(s.Range, $"%{search}%") - || EF.Functions.Like(s.Components, $"%{search}%") + || EF.Functions.Like(s.Class, $"%{search}%") + || EF.Functions.Like(s.Level, $"%{search}%") + || EF.Functions.Like(s.CastingTime, $"%{search}%") + || EF.Functions.Like(s.Range, $"%{search}%") + || EF.Functions.Like(s.Components, $"%{search}%") ); var items = await query.ToListAsync(cancellationToken); diff --git a/src/Open5ETools.Core/Services/UserService.cs b/src/Open5ETools.Core/Services/UserService.cs index 6d44b9b..b60736a 100644 --- a/src/Open5ETools.Core/Services/UserService.cs +++ b/src/Open5ETools.Core/Services/UserService.cs @@ -1,4 +1,4 @@ -using AutoMapper; +using MapsterMapper; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using Open5ETools.Core.Common.Enums; @@ -7,7 +7,9 @@ using Open5ETools.Core.Common.Interfaces.Data; using Open5ETools.Core.Common.Interfaces.Services; using Open5ETools.Core.Common.Models.Services; -using Open5ETools.Core.Domain; +using Open5ETools.Resources; +using Enum = System.Enum; +using User = Open5ETools.Core.Domain.User; namespace Open5ETools.Core.Services; @@ -41,17 +43,17 @@ private static void ValidateModel(UserModel model) var errors = new List(); ArgumentNullException.ThrowIfNull(model); if (model.Password.Length < 8) - errors.Add(new ServiceException(Resources.Error.PasswordLength)); + errors.Add(new ServiceException(Error.PasswordLength)); if (string.IsNullOrEmpty(model.Username)) - errors.Add(new ServiceException(string.Format(Resources.Error.RequiredValidation, model.Username))); + errors.Add(new ServiceException(string.Format(Error.RequiredValidation, model.Username))); if (string.IsNullOrEmpty(model.FirstName)) - errors.Add(new ServiceException(string.Format(Resources.Error.RequiredValidation, model.FirstName))); + errors.Add(new ServiceException(string.Format(Error.RequiredValidation, model.FirstName))); if (string.IsNullOrEmpty(model.LastName)) - errors.Add(new ServiceException(string.Format(Resources.Error.RequiredValidation, model.LastName))); + errors.Add(new ServiceException(string.Format(Error.RequiredValidation, model.LastName))); if (string.IsNullOrEmpty(model.Email)) - errors.Add(new ServiceException(string.Format(Resources.Error.RequiredValidation, model.Email))); + errors.Add(new ServiceException(string.Format(Error.RequiredValidation, model.Email))); if (string.IsNullOrEmpty(model.Role)) - errors.Add(new ServiceException(string.Format(Resources.Error.RequiredValidation, model.Role))); + errors.Add(new ServiceException(string.Format(Error.RequiredValidation, model.Role))); if (errors.Count != 0) throw new ServiceAggregateException(errors); } @@ -59,10 +61,10 @@ private static void ValidateModel(UserModel model) private async Task CheckUserExistAsync(UserModel model, CancellationToken cancellationToken) { var user = await _context.Users - .AsNoTracking() - .FirstOrDefaultAsync(u => u.Username == model.Username, cancellationToken); + .AsNoTracking() + .FirstOrDefaultAsync(u => u.Username == model.Username, cancellationToken); if (user is not null) - throw new ServiceException(string.Format(Resources.Error.UserExist, model.Username)); + throw new ServiceException(string.Format(Error.UserExist, model.Username)); } public async Task DeleteAsync(int id, CancellationToken cancellationToken) @@ -93,8 +95,9 @@ public async Task GetAsync(int id, CancellationToken cancellationToke try { var user = await _context.Users - .AsNoTracking() - .FirstOrDefaultAsync(u => u.Id == id, cancellationToken); + .AsNoTracking() + .FirstOrDefaultAsync(u => u.Id == id, cancellationToken) ?? + throw new ServiceException(Error.NotFound); return _mapper.Map(user); } @@ -105,7 +108,8 @@ public async Task GetAsync(int id, CancellationToken cancellationToke } } - public async Task> ListAsync(bool? deleted = false, CancellationToken cancellationToken = default) + public async Task> ListAsync(bool? deleted = false, + CancellationToken cancellationToken = default) { try { @@ -114,9 +118,12 @@ public async Task> ListAsync(bool? deleted = false, Cance if (deleted.HasValue) query = query.Where(x => x.IsDeleted == deleted.Value); - return [.. (await query.ToListAsync(cancellationToken)) - .Select(_mapper.Map) - .OrderBy(um => um.Username)]; + return + [ + .. (await query.ToListAsync(cancellationToken)) + .Select(_mapper.Map) + .OrderBy(um => um.Username) + ]; } catch (Exception ex) { @@ -135,6 +142,7 @@ public async Task RestoreAsync(int id, CancellationToken cancellationToken user.IsDeleted = false; await _context.SaveChangesAsync(cancellationToken); } + return true; } catch (Exception ex) @@ -171,13 +179,13 @@ private static void ValidateModelForEdit(UserModel model) var errors = new List(); ArgumentNullException.ThrowIfNull(model); if (string.IsNullOrEmpty(model.FirstName)) - errors.Add(new ServiceException(string.Format(Resources.Error.RequiredValidation, model.FirstName))); + errors.Add(new ServiceException(string.Format(Error.RequiredValidation, model.FirstName))); if (string.IsNullOrEmpty(model.LastName)) - errors.Add(new ServiceException(string.Format(Resources.Error.RequiredValidation, model.LastName))); + errors.Add(new ServiceException(string.Format(Error.RequiredValidation, model.LastName))); if (string.IsNullOrEmpty(model.Email)) - errors.Add(new ServiceException(string.Format(Resources.Error.RequiredValidation, model.Email))); + errors.Add(new ServiceException(string.Format(Error.RequiredValidation, model.Email))); if (string.IsNullOrEmpty(model.Role)) - errors.Add(new ServiceException(string.Format(Resources.Error.RequiredValidation, model.Role))); + errors.Add(new ServiceException(string.Format(Error.RequiredValidation, model.Role))); if (errors.Count != 0) throw new ServiceAggregateException(errors); } @@ -187,12 +195,13 @@ public async Task ChangePasswordAsync(ChangePasswordModel model, CancellationTok try { if (model.NewPassword.Length < 8) - throw new ServiceException(Resources.Error.PasswordLength); + throw new ServiceException(Error.PasswordLength); - var user = await _context.Users.FirstOrDefaultAsync(u => u.Id == model.Id, cancellationToken) ?? throw new ServiceException(Resources.Error.NotFound); + var user = await _context.Users.FirstOrDefaultAsync(u => u.Id == model.Id, cancellationToken) ?? + throw new ServiceException(Error.NotFound); if (!PasswordHelper.CheckPassword(user.Password, model.CurrentPassword)) - throw new ServiceException(Resources.Error.PasswordMissMatch); + throw new ServiceException(Error.PasswordMissMatch); user.Password = PasswordHelper.EncryptPassword(model.NewPassword); await _context.SaveChangesAsync(cancellationToken); diff --git a/src/Open5ETools.Infrastructure/Data/AppDbContextInitializer.cs b/src/Open5ETools.Infrastructure/Data/AppDbContextInitializer.cs index 6c1c92e..d451b7f 100644 --- a/src/Open5ETools.Infrastructure/Data/AppDbContextInitializer.cs +++ b/src/Open5ETools.Infrastructure/Data/AppDbContextInitializer.cs @@ -1,4 +1,4 @@ -using AutoMapper; +using MapsterMapper; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; using Open5ETools.Core.Common.Configurations; diff --git a/src/Open5ETools.Web/Automapper/AuthProfile.cs b/src/Open5ETools.Web/Automapper/AuthProfile.cs deleted file mode 100644 index 9e8d618..0000000 --- a/src/Open5ETools.Web/Automapper/AuthProfile.cs +++ /dev/null @@ -1,13 +0,0 @@ -using AutoMapper; -using Open5ETools.Core.Common.Models.Services; -using Open5ETools.Web.Models.Auth; - -namespace Open5ETools.Web.Automapper; - -public class AuthProfile : Profile -{ - public AuthProfile() - { - CreateMap().ReverseMap(); - } -} \ No newline at end of file diff --git a/src/Open5ETools.Web/Automapper/DungeonOptionProfile.cs b/src/Open5ETools.Web/Automapper/DungeonOptionProfile.cs deleted file mode 100644 index 3dce589..0000000 --- a/src/Open5ETools.Web/Automapper/DungeonOptionProfile.cs +++ /dev/null @@ -1,25 +0,0 @@ -using AutoMapper; -using Open5ETools.Core.Common.Models.DM.Services; -using Open5ETools.Web.Models.Dungeon; -using System.Globalization; - -namespace Open5ETools.Web.Automapper; - -public class DungeonOptionProfile : Profile -{ - public DungeonOptionProfile() - { - CreateMap() - .ForMember(dest => dest.TreasureValue, opt => opt.MapFrom(src => Convert.ToDouble(src.TreasureValue, CultureInfo.InvariantCulture))) - .ForMember(dest => dest.MonsterType, opt => opt.Ignore()); - CreateMap() - .ForMember(dest => dest.TreasureValue, opt => opt.MapFrom(src => src.TreasureValue.ToString())) - .ForMember(dest => dest.MonsterType, opt => opt.MapFrom(src => GetMonsters(src))); - CreateMap().ReverseMap(); - } - - private static string[] GetMonsters(DungeonOptionModel model) - { - return model.MonsterType.Split(','); - } -} \ No newline at end of file diff --git a/src/Open5ETools.Web/Automapper/DungeonProfile.cs b/src/Open5ETools.Web/Automapper/DungeonProfile.cs deleted file mode 100644 index 4e7e294..0000000 --- a/src/Open5ETools.Web/Automapper/DungeonProfile.cs +++ /dev/null @@ -1,13 +0,0 @@ -using AutoMapper; -using Open5ETools.Core.Common.Models.DM.Services; -using Open5ETools.Web.Models.Dungeon; - -namespace Open5ETools.Web.Automapper; - -public class DungeonProfile : Profile -{ - public DungeonProfile() - { - CreateMap().ReverseMap(); - } -} \ No newline at end of file diff --git a/src/Open5ETools.Web/Automapper/EncounterProfile.cs b/src/Open5ETools.Web/Automapper/EncounterProfile.cs deleted file mode 100644 index 9d4a95e..0000000 --- a/src/Open5ETools.Web/Automapper/EncounterProfile.cs +++ /dev/null @@ -1,18 +0,0 @@ -using AutoMapper; -using Open5ETools.Core.Common.Models.EG; -using Open5ETools.Web.Models.Encounter; - -namespace Open5ETools.Web.Automapper; - -public class EncounterProfile : Profile -{ - public EncounterProfile() - { - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - CreateMap() - .ForMember(dest => dest.Sizes, opt => opt.MapFrom(s => s.SelectedSizes)) - .ForMember(dest => dest.MonsterTypes, opt => opt.MapFrom(s => s.SelectedMonsterTypes)); - } -} \ No newline at end of file diff --git a/src/Open5ETools.Web/Automapper/ProfileProfile.cs b/src/Open5ETools.Web/Automapper/ProfileProfile.cs deleted file mode 100644 index 2e8bec3..0000000 --- a/src/Open5ETools.Web/Automapper/ProfileProfile.cs +++ /dev/null @@ -1,14 +0,0 @@ -using AutoMapper; -using Open5ETools.Core.Common.Models.Services; -using Open5ETools.Web.Models.Profile; - -namespace Open5ETools.Web.Automapper; - -public class ProfileProfile : Profile -{ - public ProfileProfile() - { - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - } -} \ No newline at end of file diff --git a/src/Open5ETools.Web/Automapper/SpellProfile.cs b/src/Open5ETools.Web/Automapper/SpellProfile.cs deleted file mode 100644 index bc68da1..0000000 --- a/src/Open5ETools.Web/Automapper/SpellProfile.cs +++ /dev/null @@ -1,13 +0,0 @@ -using AutoMapper; -using Open5ETools.Core.Common.Models.SM; -using Open5ETools.Web.Models.Spell; - -namespace Open5ETools.Web.Automapper; - -public class SpellProfile : Profile -{ - public SpellProfile() - { - CreateMap().ReverseMap(); - } -} \ No newline at end of file diff --git a/src/Open5ETools.Web/Automapper/UserProfile.cs b/src/Open5ETools.Web/Automapper/UserProfile.cs deleted file mode 100644 index 818e885..0000000 --- a/src/Open5ETools.Web/Automapper/UserProfile.cs +++ /dev/null @@ -1,14 +0,0 @@ -using AutoMapper; -using Open5ETools.Core.Common.Models.Services; -using Open5ETools.Web.Models.User; - -namespace Open5ETools.Web.Automapper; - -public class UserProfile : Profile -{ - public UserProfile() - { - CreateMap().ReverseMap(); - CreateMap().ReverseMap(); - } -} \ No newline at end of file diff --git a/src/Open5ETools.Web/ConfigureServices.cs b/src/Open5ETools.Web/ConfigureServices.cs index 7f0b53c..6d9f63b 100644 --- a/src/Open5ETools.Web/ConfigureServices.cs +++ b/src/Open5ETools.Web/ConfigureServices.cs @@ -6,7 +6,11 @@ using Open5ETools.Web.Services; using Serilog; using System.Globalization; -using Open5ETools.Web.Automapper; +using Mapster; +using Open5ETools.Core.Common.Models.DM.Services; +using Open5ETools.Core.Common.Models.EG; +using Open5ETools.Web.Models.Dungeon; +using Open5ETools.Web.Models.Encounter; namespace Open5ETools.Web; @@ -17,23 +21,22 @@ public static IServiceCollection AddWebServices(this IServiceCollection services services.AddHttpContextAccessor() .AddScoped(); - services.Configure( - opts => + services.Configure(opts => + { + var supportedCultures = new List { - var supportedCultures = new List - { - new("hu"), - new("en") - }; - opts.DefaultRequestCulture = new RequestCulture("en"); - opts.SupportedCultures = supportedCultures; - opts.SupportedUICultures = supportedCultures; - opts.RequestCultureProviders = - [ - new QueryStringRequestCultureProvider(), - new CookieRequestCultureProvider() - ]; - }); + new("hu"), + new("en") + }; + opts.DefaultRequestCulture = new RequestCulture("en"); + opts.SupportedCultures = supportedCultures; + opts.SupportedUICultures = supportedCultures; + opts.RequestCultureProviders = + [ + new QueryStringRequestCultureProvider(), + new CookieRequestCultureProvider() + ]; + }); services.Configure(options => { @@ -48,8 +51,8 @@ public static IServiceCollection AddWebServices(this IServiceCollection services options.AccessDeniedPath = new PathString("/Auth/Forbidden/"); }); - services.AddAutoMapper(cfg => { cfg.AllowNullCollections = true; }, typeof(AuthProfile)); - services.AddMemoryCache(); + services.ConfigureMapster() + .AddMemoryCache(); services.AddMvc() #if DEBUG @@ -62,6 +65,33 @@ public static IServiceCollection AddWebServices(this IServiceCollection services return services; } + private static IServiceCollection ConfigureMapster(this IServiceCollection services) + { + services.AddMapster(); + + TypeAdapterConfig + .NewConfig() + .Map(dest => dest.Sizes, src => src.SelectedSizes) + .Map(dest => dest.MonsterTypes, src => src.SelectedMonsterTypes); + + TypeAdapterConfig + .NewConfig() + .Map(dest => dest.TreasureValue, src => Convert.ToDouble(src.TreasureValue, CultureInfo.InvariantCulture)) + .Ignore(dest => dest.MonsterType); + + TypeAdapterConfig + .NewConfig() + .Map(dest => dest.TreasureValue, src => src.TreasureValue.ToString(CultureInfo.InvariantCulture)) + .Map(dest => dest.MonsterType, src => GetMonsters(src)); + + return services; + } + + private static string[] GetMonsters(DungeonOptionModel model) + { + return model.MonsterType.Split(','); + } + public static IHostBuilder AddSerilog(this IHostBuilder host, IConfiguration configuration) { diff --git a/src/Open5ETools.Web/Controllers/Auth/AuthController.cs b/src/Open5ETools.Web/Controllers/Auth/AuthController.cs index 9d7d699..d32f4a4 100644 --- a/src/Open5ETools.Web/Controllers/Auth/AuthController.cs +++ b/src/Open5ETools.Web/Controllers/Auth/AuthController.cs @@ -1,4 +1,4 @@ -using AutoMapper; +using MapsterMapper; using IdentityModel; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; diff --git a/src/Open5ETools.Web/Controllers/Web/DungeonController.cs b/src/Open5ETools.Web/Controllers/Web/DungeonController.cs index 274e262..408cc58 100644 --- a/src/Open5ETools.Web/Controllers/Web/DungeonController.cs +++ b/src/Open5ETools.Web/Controllers/Web/DungeonController.cs @@ -1,4 +1,4 @@ -using AutoMapper; +using MapsterMapper; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Rendering; @@ -9,11 +9,14 @@ using Open5ETools.Core.Common.Models.DM.Services; using Open5ETools.Web.Models.Dungeon; using System.Text.Json; +using Open5ETools.Core.Common.Exceptions; +using Open5ETools.Resources; namespace Open5ETools.Web.Controllers.Web; [Authorize] -public class DungeonController(IDungeonService dungeonService, +public class DungeonController( + IDungeonService dungeonService, IOptionService optionService, ICurrentUserService currentUserService, IMapper mapper, @@ -28,7 +31,8 @@ public class DungeonController(IDungeonService dungeonService, public async Task Index(CancellationToken cancellationToken) { var list = - await _dungeonService.GetAllDungeonOptionsForUserAsync(_currentUserService.GetUserIdAsInt(), cancellationToken); + await _dungeonService.GetAllDungeonOptionsForUserAsync(_currentUserService.GetUserIdAsInt(), + cancellationToken); var model = new DungeonListViewModel { List = list.Select(_mapper.Map) @@ -42,9 +46,11 @@ public async Task Load(string name, int level, CancellationToken var model = new LoadViewModel { Theme = string.Empty, - Option = _mapper.Map(await _dungeonService.GetDungeonOptionByNameAsync(name, _currentUserService.GetUserIdAsInt(), cancellationToken)), + Option = _mapper.Map( + await _dungeonService.GetDungeonOptionByNameAsync(name, _currentUserService.GetUserIdAsInt(), + cancellationToken) ?? throw new ServiceException(Error.NotFound)), Themes = (await _optionService.ListOptionsAsync(OptionKey.Theme, cancellationToken)) - .Select(om => new SelectListItem { Text = om.Name, Value = om.Value, Selected = true }) + .Select(om => new SelectListItem { Text = om.Name, Value = om.Value, Selected = true }) }; if (level != 0) @@ -66,6 +72,7 @@ public async Task Delete(int id, CancellationToken cancellationTo { _logger.LogError(ex, "Error deleting dungeon."); } + return RedirectToAction(nameof(Index)); } @@ -87,18 +94,24 @@ public async Task Rename(DungeonRenameViewModel model, Cancellati { try { - var existing = await _dungeonService.GetDungeonOptionByNameAsync(model.NewDungeonName, model.UserId, cancellationToken); + var existing = + await _dungeonService.GetDungeonOptionByNameAsync(model.NewDungeonName, model.UserId, + cancellationToken); if (existing is null) { - await _dungeonService.RenameDungeonAsync(model.Id, model.UserId, model.NewDungeonName, cancellationToken); + await _dungeonService.RenameDungeonAsync(model.Id, model.UserId, model.NewDungeonName, + cancellationToken); return RedirectToAction("Index"); } - ModelState.AddModelError(nameof(model.NewDungeonName), string.Format(Resources.Error.DungeonExist, model.NewDungeonName)); + + ModelState.AddModelError(nameof(model.NewDungeonName), + string.Format(Error.DungeonExist, model.NewDungeonName)); } catch (Exception ex) { _logger.LogError(ex, "Error renaming dungeon."); } + return View(model); } @@ -114,10 +127,12 @@ public async Task DeleteOption(int id, CancellationToken cancella { _logger.LogError(ex, "Error deleting dungeon option."); } + return RedirectToAction(nameof(Index)); } - private async Task GetMonsterTypeAsync(DungeonOptionCreateViewModel model, CancellationToken cancellationToken) + private async Task GetMonsterTypeAsync(DungeonOptionCreateViewModel model, + CancellationToken cancellationToken) { var monsterType = string.Empty; var monsters = await _optionService.ListOptionsAsync(OptionKey.MonsterType, cancellationToken); @@ -133,6 +148,7 @@ private async Task GetMonsterTypeAsync(DungeonOptionCreateViewModel mode { monsterType = string.Join(",", model.MonsterType); } + return monsterType; } @@ -146,7 +162,8 @@ public async Task Create(DungeonOptionCreateViewModel model, Canc { var optionModel = _mapper.Map(model); optionModel.MonsterType = await GetMonsterTypeAsync(model, cancellationToken); - var dungeon = await _dungeonService.CreateOrUpdateDungeonAsync(optionModel, model.AddDungeon, model.Level, cancellationToken); + var dungeon = await _dungeonService.CreateOrUpdateDungeonAsync(optionModel, model.AddDungeon, + model.Level, cancellationToken); return Json(JsonSerializer.Serialize(dungeon)); } catch (Exception ex) @@ -154,6 +171,7 @@ public async Task Create(DungeonOptionCreateViewModel model, Canc _logger.LogError(ex, "Error creating dungeon."); } } + await FillCreateModelDropDownsAsync(model, cancellationToken); return View(model); } @@ -186,23 +204,34 @@ public async Task Create(int optionId, CancellationToken cancella return View(model); } - private async Task FillCreateModelDropDownsAsync(DungeonOptionCreateViewModel model, CancellationToken cancellationToken) + private async Task FillCreateModelDropDownsAsync(DungeonOptionCreateViewModel model, + CancellationToken cancellationToken) { var options = await _optionService.ListOptionsAsync(null, cancellationToken); var optionModels = options.ToList(); - model.DungeonSizes = optionModels.Where(om => om.Key == OptionKey.Size).Select(om => new SelectListItem { Text = om.Name, Value = om.Value }); - model.DungeonDifficulties = optionModels.Where(om => om.Key == OptionKey.Difficulty).Select(om => new SelectListItem { Text = om.Name, Value = om.Value }); + model.DungeonSizes = optionModels.Where(om => om.Key == OptionKey.Size) + .Select(om => new SelectListItem { Text = om.Name, Value = om.Value }); + model.DungeonDifficulties = optionModels.Where(om => om.Key == OptionKey.Difficulty) + .Select(om => new SelectListItem { Text = om.Name, Value = om.Value }); model.PartyLevels = SelectListHelper.GenerateIntSelectList(1, 20); model.PartySizes = SelectListHelper.GenerateIntSelectList(1, 10); - model.TreasureValues = optionModels.Where(om => om.Key == OptionKey.TreasureValue).Select(om => new SelectListItem { Text = om.Name, Value = om.Value }); - model.ItemsRarities = optionModels.Where(om => om.Key == OptionKey.ItemsRarity).Select(om => new SelectListItem { Text = om.Name, Value = om.Value }); - model.RoomDensities = optionModels.Where(om => om.Key == OptionKey.RoomDensity).Select(om => new SelectListItem { Text = om.Name, Value = om.Value }); - model.RoomSizes = optionModels.Where(om => om.Key == OptionKey.RoomSize).Select(om => new SelectListItem { Text = om.Name, Value = om.Value }); - model.MonsterTypes = optionModels.Where(om => om.Key == OptionKey.MonsterType).Select(om => new SelectListItem { Text = om.Name, Value = om.Value, Selected = true }); - model.TrapPercents = optionModels.Where(om => om.Key == OptionKey.TrapPercent).Select(om => new SelectListItem { Text = om.Name, Value = om.Value, Selected = true }); + model.TreasureValues = optionModels.Where(om => om.Key == OptionKey.TreasureValue) + .Select(om => new SelectListItem { Text = om.Name, Value = om.Value }); + model.ItemsRarities = optionModels.Where(om => om.Key == OptionKey.ItemsRarity) + .Select(om => new SelectListItem { Text = om.Name, Value = om.Value }); + model.RoomDensities = optionModels.Where(om => om.Key == OptionKey.RoomDensity) + .Select(om => new SelectListItem { Text = om.Name, Value = om.Value }); + model.RoomSizes = optionModels.Where(om => om.Key == OptionKey.RoomSize) + .Select(om => new SelectListItem { Text = om.Name, Value = om.Value }); + model.MonsterTypes = optionModels.Where(om => om.Key == OptionKey.MonsterType).Select(om => + new SelectListItem { Text = om.Name, Value = om.Value, Selected = true }); + model.TrapPercents = optionModels.Where(om => om.Key == OptionKey.TrapPercent).Select(om => + new SelectListItem { Text = om.Name, Value = om.Value, Selected = true }); model.DeadEnds = SelectListHelper.GetBool(); model.Corridors = SelectListHelper.GetBool(); - model.RoamingPercents = optionModels.Where(om => om.Key == OptionKey.RoamingPercent).Select(om => new SelectListItem { Text = om.Name, Value = om.Value, Selected = true }); - model.Themes = optionModels.Where(om => om.Key == OptionKey.Theme).Select(om => new SelectListItem { Text = om.Name, Value = om.Value, Selected = true }); + model.RoamingPercents = optionModels.Where(om => om.Key == OptionKey.RoamingPercent) + .Select(om => new SelectListItem { Text = om.Name, Value = om.Value, Selected = true }); + model.Themes = optionModels.Where(om => om.Key == OptionKey.Theme).Select(om => new SelectListItem + { Text = om.Name, Value = om.Value, Selected = true }); } } \ No newline at end of file diff --git a/src/Open5ETools.Web/Controllers/Web/EncounterController.cs b/src/Open5ETools.Web/Controllers/Web/EncounterController.cs index 5f1bcdf..b722009 100644 --- a/src/Open5ETools.Web/Controllers/Web/EncounterController.cs +++ b/src/Open5ETools.Web/Controllers/Web/EncounterController.cs @@ -1,4 +1,4 @@ -using AutoMapper; +using MapsterMapper; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Rendering; diff --git a/src/Open5ETools.Web/Controllers/Web/ProfileController.cs b/src/Open5ETools.Web/Controllers/Web/ProfileController.cs index cd90b2f..cea604c 100644 --- a/src/Open5ETools.Web/Controllers/Web/ProfileController.cs +++ b/src/Open5ETools.Web/Controllers/Web/ProfileController.cs @@ -1,4 +1,4 @@ -using AutoMapper; +using MapsterMapper; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Open5ETools.Core.Common.Interfaces.Services; diff --git a/src/Open5ETools.Web/Controllers/Web/SpellController.cs b/src/Open5ETools.Web/Controllers/Web/SpellController.cs index 3b37b2f..c220927 100644 --- a/src/Open5ETools.Web/Controllers/Web/SpellController.cs +++ b/src/Open5ETools.Web/Controllers/Web/SpellController.cs @@ -1,4 +1,4 @@ -using AutoMapper; +using MapsterMapper; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Open5ETools.Core.Common.Interfaces.Services.SM; diff --git a/src/Open5ETools.Web/Controllers/Web/UserController.cs b/src/Open5ETools.Web/Controllers/Web/UserController.cs index 9111270..726d569 100644 --- a/src/Open5ETools.Web/Controllers/Web/UserController.cs +++ b/src/Open5ETools.Web/Controllers/Web/UserController.cs @@ -1,4 +1,4 @@ -using AutoMapper; +using MapsterMapper; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Open5ETools.Core.Common.Interfaces.Services; diff --git a/src/Open5ETools.Web/Open5ETools.Web.csproj b/src/Open5ETools.Web/Open5ETools.Web.csproj index de487af..07be468 100644 --- a/src/Open5ETools.Web/Open5ETools.Web.csproj +++ b/src/Open5ETools.Web/Open5ETools.Web.csproj @@ -3,7 +3,7 @@ true true - 1.0.2.1 + 1.0.3.0 diff --git a/src/Open5ETools.Web/Views/Dungeon/Load.cshtml b/src/Open5ETools.Web/Views/Dungeon/Load.cshtml index 9962c5b..3a13e34 100644 --- a/src/Open5ETools.Web/Views/Dungeon/Load.cshtml +++ b/src/Open5ETools.Web/Views/Dungeon/Load.cshtml @@ -5,7 +5,8 @@ }
- +
@@ -14,7 +15,7 @@ - +
@@ -27,59 +28,60 @@
- @foreach (var dungeon in Model.Option.Dungeons) + @foreach (var dungeon in Model.Option.Dungeons.OrderBy(d => d.Level)) {

@Resources.Dungeon.Level - @dungeon.Level

- +
- +
}
@section Scripts { - + } \ No newline at end of file diff --git a/src/Open5ETools.Web/libman.json b/src/Open5ETools.Web/libman.json index 9f65deb..bc8d7b6 100644 --- a/src/Open5ETools.Web/libman.json +++ b/src/Open5ETools.Web/libman.json @@ -1,5 +1,5 @@ { - "version": "1.0", + "version": "3.0", "defaultProvider": "cdnjs", "libraries": [ { @@ -11,7 +11,7 @@ "destination": "wwwroot/lib/bootstrap" }, { - "library": "bootbox.js@6.0.2", + "library": "bootbox.js@6.0.4", "destination": "wwwroot/lib/bootbox" }, { diff --git a/tests/Open5ETools.Core.Tests/Open5ETools.Core.Tests.csproj b/tests/Open5ETools.Core.Tests/Open5ETools.Core.Tests.csproj index 3f09529..8098e34 100644 --- a/tests/Open5ETools.Core.Tests/Open5ETools.Core.Tests.csproj +++ b/tests/Open5ETools.Core.Tests/Open5ETools.Core.Tests.csproj @@ -16,7 +16,6 @@ - diff --git a/tests/Open5ETools.Core.Tests/SpellServiceTests/Get.cs b/tests/Open5ETools.Core.Tests/SpellServiceTests/Get.cs index 8ecdcf2..88a268f 100644 --- a/tests/Open5ETools.Core.Tests/SpellServiceTests/Get.cs +++ b/tests/Open5ETools.Core.Tests/SpellServiceTests/Get.cs @@ -14,9 +14,7 @@ public class Get(TestFixture fixture) : IClassFixture [InlineData(256)] public async Task GetAsync_WithValidId_ReturnsSpell(int id) { - using var source = new CancellationTokenSource(); - var token = source.Token; - var result = await _spellService.GetAsync(id, token); + var result = await _spellService.GetAsync(id, TestContext.Current.CancellationToken); result.ShouldNotBeNull(); result.Id.ShouldBe(id); }