From cb7ad7d0f0f1364e2394d9681ddeaa74853faa8c Mon Sep 17 00:00:00 2001 From: Levi Zitting Date: Tue, 7 Jul 2026 10:44:34 -0500 Subject: [PATCH 1/6] refactor: replace AutoMapper with Riok.Mapperly source generator --- .../Controllers/ConferenceIssueController.cs | 8 +-- .../ConferenceScheduleController.cs | 6 +-- .../Controllers/SessionFeedbackController.cs | 8 +-- .../MethodConf.Cms/Mapping/DefaultProfile.cs | 33 ------------ .../src/MethodConf.Cms/Mapping/IssueMapper.cs | 50 +++++++++++++++++++ .../MethodConf.Cms/Mapping/ScheduleMapper.cs | 35 +++++++++++++ .../Mapping/SessionFeedbackMapper.cs | 33 ++++++++++++ .../Mapping/SessionUrlSegmentResolver.cs | 21 -------- .../src/MethodConf.Cms/MethodConf.Cms.csproj | 2 +- .../Services/ConferenceIssueService.cs | 14 +++--- .../Services/ConferenceScheduleService.cs | 8 +-- .../Services/DependencyInjection.cs | 4 +- .../Services/SessionFeedbackService.cs | 6 +-- 13 files changed, 147 insertions(+), 81 deletions(-) delete mode 100644 backend/src/MethodConf.Cms/Mapping/DefaultProfile.cs create mode 100644 backend/src/MethodConf.Cms/Mapping/IssueMapper.cs create mode 100644 backend/src/MethodConf.Cms/Mapping/ScheduleMapper.cs create mode 100644 backend/src/MethodConf.Cms/Mapping/SessionFeedbackMapper.cs delete mode 100644 backend/src/MethodConf.Cms/Mapping/SessionUrlSegmentResolver.cs diff --git a/backend/src/MethodConf.Cms/Controllers/ConferenceIssueController.cs b/backend/src/MethodConf.Cms/Controllers/ConferenceIssueController.cs index ce07e82..1d64586 100644 --- a/backend/src/MethodConf.Cms/Controllers/ConferenceIssueController.cs +++ b/backend/src/MethodConf.Cms/Controllers/ConferenceIssueController.cs @@ -1,8 +1,8 @@ using Asp.Versioning; -using AutoMapper; using MethodConf.Cms.Domain; using MethodConf.Cms.Domain.Errors; using MethodConf.Cms.Dtos; +using MethodConf.Cms.Mapping; using MethodConf.Cms.Services.Interfaces; using Microsoft.AspNetCore.Mvc; @@ -11,19 +11,19 @@ namespace MethodConf.Cms.Controllers; [ApiController] [ApiVersion("1.0")] [Route(RouteTemplates.ConferenceIssue)] -public class ConferenceIssueController(IConferenceIssueService conferenceIssueService, IMapper mapper) : Controller +public class ConferenceIssueController(IConferenceIssueService conferenceIssueService, IssueMapper mapper) : Controller { [HttpPost] public async Task> CreateIssue(Guid conferenceId, CreateIssueRequestDto request) { - var createIssue = mapper.Map(request); + var createIssue = mapper.ToCreateIssue(request); var result = await conferenceIssueService.CreateIssue(conferenceId, createIssue); return result switch { { IsFailed: true } when result.Errors.Any(e => e is InvalidEntityIdError) => NotFound(result.Errors), - { IsSuccess: true } => Ok(mapper.Map(result.Value)), + { IsSuccess: true } => Ok(mapper.ToCreateIssueResponseDto(result.Value)), _ => StatusCode(500) }; } diff --git a/backend/src/MethodConf.Cms/Controllers/ConferenceScheduleController.cs b/backend/src/MethodConf.Cms/Controllers/ConferenceScheduleController.cs index 68af0de..d876c52 100644 --- a/backend/src/MethodConf.Cms/Controllers/ConferenceScheduleController.cs +++ b/backend/src/MethodConf.Cms/Controllers/ConferenceScheduleController.cs @@ -1,7 +1,7 @@ using Asp.Versioning; -using AutoMapper; using MethodConf.Cms.Domain.Errors; using MethodConf.Cms.Dtos; +using MethodConf.Cms.Mapping; using MethodConf.Cms.Services.Interfaces; using Microsoft.AspNetCore.Mvc; @@ -10,7 +10,7 @@ namespace MethodConf.Cms.Controllers; [ApiController] [ApiVersion("1.0")] [Route(RouteTemplates.ConferenceSchedule)] -public class ConferenceScheduleController(IConferenceScheduleService conferenceScheduleService, IMapper mapper) : Controller +public class ConferenceScheduleController(IConferenceScheduleService conferenceScheduleService, ScheduleMapper mapper) : Controller { [HttpGet] public ActionResult GetSchedule(Guid conferenceId) @@ -20,7 +20,7 @@ public ActionResult GetSchedule(Guid conferenceId return result switch { { IsFailed: true } when result.Errors.Any(e => e is InvalidEntityIdError) => NotFound(result.Errors), - { IsSuccess: true } => Ok(mapper.Map(result.Value)), + { IsSuccess: true } => Ok(mapper.ToDto(result.Value)), _ => StatusCode(500) }; } diff --git a/backend/src/MethodConf.Cms/Controllers/SessionFeedbackController.cs b/backend/src/MethodConf.Cms/Controllers/SessionFeedbackController.cs index 342d787..ab54855 100644 --- a/backend/src/MethodConf.Cms/Controllers/SessionFeedbackController.cs +++ b/backend/src/MethodConf.Cms/Controllers/SessionFeedbackController.cs @@ -1,7 +1,7 @@ -using AutoMapper; using MethodConf.Cms.Domain; using MethodConf.Cms.Domain.Errors; using MethodConf.Cms.Dtos; +using MethodConf.Cms.Mapping; using MethodConf.Cms.Services.Interfaces; using Microsoft.AspNetCore.Mvc; @@ -9,19 +9,19 @@ namespace MethodConf.Cms.Controllers; [ApiController] [Route(RouteTemplates.SessionFeedback)] -public class SessionFeedbackController(ISessionFeedbackService sessionFeedbackService, IMapper mapper) : Controller +public class SessionFeedbackController(ISessionFeedbackService sessionFeedbackService, SessionFeedbackMapper mapper) : Controller { [HttpPost] public async Task> Create(Guid sessionId, CreateSessionFeedbackRequestDto request) { - var createSessionFeedback = mapper.Map(request); + var createSessionFeedback = mapper.ToCreateSessionFeedback(request); var result = await sessionFeedbackService.Create(sessionId, createSessionFeedback); return result switch { { IsFailed: true } when result.Errors.Any(e => e is InvalidEntityIdError) => BadRequest(result.Errors), - { IsSuccess: true } => Ok(mapper.Map(result.Value)), + { IsSuccess: true } => Ok(mapper.ToDto(result.Value)), _ => StatusCode(500) }; } diff --git a/backend/src/MethodConf.Cms/Mapping/DefaultProfile.cs b/backend/src/MethodConf.Cms/Mapping/DefaultProfile.cs deleted file mode 100644 index b05dc30..0000000 --- a/backend/src/MethodConf.Cms/Mapping/DefaultProfile.cs +++ /dev/null @@ -1,33 +0,0 @@ -using AutoMapper; -using MethodConf.Cms.Domain; -using MethodConf.Cms.Dtos; -using Umbraco.Cms.Web.Common.PublishedModels; - -namespace MethodConf.Cms.Mapping; - -public class DefaultProfile : Profile -{ - public DefaultProfile() - { - CreateMap(); - CreateMap() - .ForMember(dest => dest.Key, opts => opts.MapFrom()); - CreateMap() - .ForMember(dest => dest.Sessions, opts => opts.MapFrom((src, _) => src.Children() ?? [])); - - CreateMap(); - CreateMap(); - CreateMap(); - - CreateMap() - .ForMember(dest => dest.PhoneNumber, opts => opts.MapFrom(src => src.Phone)); - CreateMap(); - CreateMap(); - CreateMap() - .ForMember(dest => dest.Phone, opts => opts.MapFrom(src => src.PhoneNumber)); - CreateMap() - .ForMember(dest => dest.Phone, opts => opts.MapFrom(src => src.PhoneNumber)); - CreateMap() - .ForMember(dest => dest.Phone, opts => opts.MapFrom(src => src.PhoneNumber)); - } -} diff --git a/backend/src/MethodConf.Cms/Mapping/IssueMapper.cs b/backend/src/MethodConf.Cms/Mapping/IssueMapper.cs new file mode 100644 index 0000000..1ba81b2 --- /dev/null +++ b/backend/src/MethodConf.Cms/Mapping/IssueMapper.cs @@ -0,0 +1,50 @@ +using MethodConf.Cms.Domain; +using MethodConf.Cms.Dtos; +using Riok.Mapperly.Abstractions; + +namespace MethodConf.Cms.Mapping; + +[Mapper] +public partial class IssueMapper +{ + [MapProperty(nameof(CreateIssueRequestDto.Phone), nameof(CreateIssue.PhoneNumber))] + public partial CreateIssue ToCreateIssue(CreateIssueRequestDto src); + + [MapperIgnoreTarget(nameof(Issue.Id))] + [MapperIgnoreTarget(nameof(Issue.ConferenceId))] + [MapperIgnoreTarget(nameof(Issue.CreatedAt))] + public partial Issue ToIssue(CreateIssue src); + + [MapperIgnoreSource(nameof(IssueWithResponse.Id))] + [MapperIgnoreSource(nameof(IssueWithResponse.ConferenceId))] + [MapProperty(nameof(IssueWithResponse.PhoneNumber), nameof(CreateIssueResponseDto.Phone))] + public partial CreateIssueResponseDto ToCreateIssueResponseDto(IssueWithResponse src); + + [MapperIgnoreSource(nameof(Issue.Id))] + [MapperIgnoreSource(nameof(Issue.ConferenceId))] + [MapperIgnoreSource(nameof(Issue.CreatedAt))] + [MapperIgnoreTarget(nameof(NewIssueEmailViewModel.Title))] + [MapProperty(nameof(Issue.PhoneNumber), nameof(NewIssueEmailViewModel.Phone))] + public partial NewIssueEmailViewModel ToNewIssueEmailViewModel(Issue src); + + [MapperIgnoreSource(nameof(Issue.Id))] + [MapperIgnoreSource(nameof(Issue.ConferenceId))] + [MapperIgnoreSource(nameof(Issue.CreatedAt))] + [MapProperty(nameof(Issue.PhoneNumber), nameof(NewIssueAppResponseViewModel.Phone))] + public partial NewIssueAppResponseViewModel ToNewIssueAppResponseViewModel(Issue src); + + public IssueWithResponse ToIssueWithResponse(Issue src) + { + return new IssueWithResponse + { + Id = src.Id, + ConferenceId = src.ConferenceId, + Message = src.Message, + Resolution = src.Resolution, + Name = src.Name, + Email = src.Email, + PhoneNumber = src.PhoneNumber, + ResponseMarkup = "", + }; + } +} diff --git a/backend/src/MethodConf.Cms/Mapping/ScheduleMapper.cs b/backend/src/MethodConf.Cms/Mapping/ScheduleMapper.cs new file mode 100644 index 0000000..7267d01 --- /dev/null +++ b/backend/src/MethodConf.Cms/Mapping/ScheduleMapper.cs @@ -0,0 +1,35 @@ +using MethodConf.Cms.Domain; +using MethodConf.Cms.Dtos; +using Riok.Mapperly.Abstractions; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Web.Common.PublishedModels; + +namespace MethodConf.Cms.Mapping; + +[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)] +public partial class ScheduleMapper +{ + private readonly IDocumentUrlService _documentUrlService; + + public ScheduleMapper(IDocumentUrlService documentUrlService) + { + _documentUrlService = documentUrlService; + } + + public partial ConferenceScheduleResponseDto ToDto(ConferenceSchedule src); + + [MapProperty(nameof(Session.Key), nameof(SessionItem.Key), Use = nameof(ResolveSessionKey))] + public partial SessionItem ToSessionItem(Session src); + + public TrackItem ToTrackItem(Track src) + { + var sessions = src.Children() ?? []; + return new TrackItem + { + Sessions = sessions.Select(ToSessionItem).ToArray() + }; + } + + private string ResolveSessionKey(Guid key) => + _documentUrlService.GetUrlSegment(key, "", false) ?? key.ToString(); +} diff --git a/backend/src/MethodConf.Cms/Mapping/SessionFeedbackMapper.cs b/backend/src/MethodConf.Cms/Mapping/SessionFeedbackMapper.cs new file mode 100644 index 0000000..6720286 --- /dev/null +++ b/backend/src/MethodConf.Cms/Mapping/SessionFeedbackMapper.cs @@ -0,0 +1,33 @@ +using MethodConf.Cms.Domain; +using MethodConf.Cms.Dtos; +using Riok.Mapperly.Abstractions; + +namespace MethodConf.Cms.Mapping; + +[Mapper] +public partial class SessionFeedbackMapper +{ + [MapProperty(nameof(CreateSessionFeedbackRequestDto.Comments), nameof(CreateSessionFeedback.Comments), Use = nameof(NullToEmpty))] + public partial CreateSessionFeedback ToCreateSessionFeedback(CreateSessionFeedbackRequestDto src); + + [MapperIgnoreSource(nameof(SessionFeedback.SessionId))] + [MapperIgnoreSource(nameof(SessionFeedback.CreatedAt))] + public partial SessionFeedbackResponseDto ToDto(SessionFeedback src); + + public SessionFeedback ToSessionFeedback(CreateSessionFeedback src) + { + return new SessionFeedback + { + Id = Guid.Empty, + SessionId = Guid.Empty, + SpeakerRating = src.SpeakerRating, + ContentRating = src.ContentRating, + VenueRating = src.VenueRating, + Comments = src.Comments, + Name = src.Name, + Email = src.Email, + }; + } + + private static string NullToEmpty(string? value) => value ?? ""; +} diff --git a/backend/src/MethodConf.Cms/Mapping/SessionUrlSegmentResolver.cs b/backend/src/MethodConf.Cms/Mapping/SessionUrlSegmentResolver.cs deleted file mode 100644 index e4566c1..0000000 --- a/backend/src/MethodConf.Cms/Mapping/SessionUrlSegmentResolver.cs +++ /dev/null @@ -1,21 +0,0 @@ -using AutoMapper; -using MethodConf.Cms.Domain; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Web.Common.PublishedModels; - -namespace MethodConf.Cms.Mapping; - -public class SessionUrlSegmentResolver : IValueResolver -{ - private readonly IDocumentUrlService _documentUrlService; - - public SessionUrlSegmentResolver(IDocumentUrlService documentUrlService) - { - _documentUrlService = documentUrlService; - } - - public string Resolve(Session source, SessionItem destination, string destMember, ResolutionContext context) - { - return _documentUrlService.GetUrlSegment(source.Key, "", false) ?? source.Key.ToString(); - } -} diff --git a/backend/src/MethodConf.Cms/MethodConf.Cms.csproj b/backend/src/MethodConf.Cms/MethodConf.Cms.csproj index f632859..7622a31 100644 --- a/backend/src/MethodConf.Cms/MethodConf.Cms.csproj +++ b/backend/src/MethodConf.Cms/MethodConf.Cms.csproj @@ -10,7 +10,6 @@ - @@ -18,6 +17,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/backend/src/MethodConf.Cms/Services/ConferenceIssueService.cs b/backend/src/MethodConf.Cms/Services/ConferenceIssueService.cs index f1c546f..cf79aa9 100644 --- a/backend/src/MethodConf.Cms/Services/ConferenceIssueService.cs +++ b/backend/src/MethodConf.Cms/Services/ConferenceIssueService.cs @@ -1,9 +1,9 @@ -using AutoMapper; using FluentResults; using MethodConf.Cms.Domain; using MethodConf.Cms.Domain.Errors; using MethodConf.Cms.Dtos; using MethodConf.Cms.Infrastructure; +using MethodConf.Cms.Mapping; using MethodConf.Cms.Services.Interfaces; using Microsoft.Extensions.Options; using Razor.Templating.Core; @@ -21,7 +21,7 @@ public class ConferenceIssueService( IEmailSender emailSender, IRazorTemplateEngine razorEngine, IOptions globalSettings, - IMapper mapper) : IConferenceIssueService + IssueMapper mapper) : IConferenceIssueService { private const string OrganizerEmailSubject = "New Issue Reported for Method Conf"; private const string ReporterEmailSubject = "Your Issue Has Been Recieved"; @@ -33,7 +33,7 @@ public async Task> CreateIssue(Guid conferenceId, Crea return Result.Fail(new InvalidEntityIdError(conferenceId.ToString())); } - var newIssue = mapper.Map(createIssue); + var newIssue = mapper.ToIssue(createIssue); newIssue.ConferenceId = conference.Key; newIssue.CreatedAt = DateTime.UtcNow; @@ -56,7 +56,7 @@ public async Task> CreateIssue(Guid conferenceId, Crea await SendOrganizerEmail(newIssue); } - var issueWithResponse = mapper.Map(newIssue); + var issueWithResponse = mapper.ToIssueWithResponse(newIssue); issueWithResponse.ResponseMarkup = await GetResponseMessage(newIssue) ?? ""; return issueWithResponse; @@ -64,7 +64,7 @@ public async Task> CreateIssue(Guid conferenceId, Crea private async Task SendOrganizerEmail(Issue issue) { - var vm = mapper.Map(issue); + var vm = mapper.ToNewIssueEmailViewModel(issue); vm.Title = OrganizerEmailSubject; var content = await razorEngine.RenderAsync("~/Views/Templates/NewIssueOrganizerEmail.cshtml", vm); @@ -82,7 +82,7 @@ private async Task SendOrganizerEmail(Issue issue) private async Task SendReporterEmail(string reporterEmail, Issue issue) { - var vm = mapper.Map(issue); + var vm = mapper.ToNewIssueEmailViewModel(issue); vm.Title = ReporterEmailSubject; var content = await razorEngine.RenderAsync("~/Views/Templates/NewIssueReporterEmail.cshtml", vm); @@ -102,7 +102,7 @@ private async Task SendReporterEmail(string reporterEmail, Issue issue) public async Task GetResponseMessage(Issue issue) { - var vm = mapper.Map(issue); + var vm = mapper.ToNewIssueAppResponseViewModel(issue); return await razorEngine.RenderAsync("~/Views/Templates/NewIssueAppResponse.cshtml", vm); } } diff --git a/backend/src/MethodConf.Cms/Services/ConferenceScheduleService.cs b/backend/src/MethodConf.Cms/Services/ConferenceScheduleService.cs index 4d84de6..3420719 100644 --- a/backend/src/MethodConf.Cms/Services/ConferenceScheduleService.cs +++ b/backend/src/MethodConf.Cms/Services/ConferenceScheduleService.cs @@ -1,7 +1,7 @@ -using AutoMapper; using FluentResults; using MethodConf.Cms.Domain; using MethodConf.Cms.Domain.Errors; +using MethodConf.Cms.Mapping; using MethodConf.Cms.Services.Interfaces; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Web; @@ -9,7 +9,7 @@ namespace MethodConf.Cms.Services; -public class ConferenceScheduleService(IPublishedContentQuery publishedContentQuery, IScheduleGridGenerator scheduleGridGenerator, IMapper mapper) : IConferenceScheduleService +public class ConferenceScheduleService(IPublishedContentQuery publishedContentQuery, IScheduleGridGenerator scheduleGridGenerator, ScheduleMapper mapper) : IConferenceScheduleService { public Result GetSchedule(Guid conferenceId) { @@ -27,10 +27,10 @@ public Result GetSchedule(Guid conferenceId) switch (sessionOrTrack) { case Session session: - scheduleItems.Add(mapper.Map(session)); + scheduleItems.Add(mapper.ToSessionItem(session)); continue; case Track track: - scheduleItems.Add(mapper.Map(track)); + scheduleItems.Add(mapper.ToTrackItem(track)); continue; } } diff --git a/backend/src/MethodConf.Cms/Services/DependencyInjection.cs b/backend/src/MethodConf.Cms/Services/DependencyInjection.cs index 3a048d7..b0c6c86 100644 --- a/backend/src/MethodConf.Cms/Services/DependencyInjection.cs +++ b/backend/src/MethodConf.Cms/Services/DependencyInjection.cs @@ -11,7 +11,9 @@ public static IHostApplicationBuilder AddApplicationServices(this IHostApplicati builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); - builder.Services.AddAutoMapper(_ => { }, typeof(DefaultProfile).Assembly); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); builder.Services.AddRazorTemplating(); return builder; diff --git a/backend/src/MethodConf.Cms/Services/SessionFeedbackService.cs b/backend/src/MethodConf.Cms/Services/SessionFeedbackService.cs index ee0d162..360e6fe 100644 --- a/backend/src/MethodConf.Cms/Services/SessionFeedbackService.cs +++ b/backend/src/MethodConf.Cms/Services/SessionFeedbackService.cs @@ -1,15 +1,15 @@ -using AutoMapper; using FluentResults; using MethodConf.Cms.Domain; using MethodConf.Cms.Domain.Errors; using MethodConf.Cms.Infrastructure; +using MethodConf.Cms.Mapping; using MethodConf.Cms.Services.Interfaces; using Umbraco.Cms.Core; using Umbraco.Cms.Web.Common.PublishedModels; namespace MethodConf.Cms.Services; -public class SessionFeedbackService(IPublishedContentQuery publishedContentQuery, AppDbContext dbContext, IMapper mapper) : ISessionFeedbackService +public class SessionFeedbackService(IPublishedContentQuery publishedContentQuery, AppDbContext dbContext, SessionFeedbackMapper mapper) : ISessionFeedbackService { public async Task> Create(Guid sessionId, CreateSessionFeedback createSessionFeedback) @@ -24,7 +24,7 @@ public async Task> Create(Guid sessionId, CreateSessionF return new IneligibleForFeedback(session); } - var sessionFeedback = mapper.Map(createSessionFeedback); + var sessionFeedback = mapper.ToSessionFeedback(createSessionFeedback); sessionFeedback.SessionId = session.Key; sessionFeedback.CreatedAt = DateTime.UtcNow; From f2c85a0a1de2f46f632827679d04687ea5431df0 Mon Sep 17 00:00:00 2001 From: Levi Zitting Date: Tue, 7 Jul 2026 10:52:26 -0500 Subject: [PATCH 2/6] refactor: replace manual mapper methods with declarative attributes --- .../src/MethodConf.Cms/Mapping/IssueMapper.cs | 17 +++------------- .../MethodConf.Cms/Mapping/ScheduleMapper.cs | 10 +++++----- .../Mapping/SessionFeedbackMapper.cs | 20 ++++++------------- 3 files changed, 14 insertions(+), 33 deletions(-) diff --git a/backend/src/MethodConf.Cms/Mapping/IssueMapper.cs b/backend/src/MethodConf.Cms/Mapping/IssueMapper.cs index 1ba81b2..b2d5d69 100644 --- a/backend/src/MethodConf.Cms/Mapping/IssueMapper.cs +++ b/backend/src/MethodConf.Cms/Mapping/IssueMapper.cs @@ -33,18 +33,7 @@ public partial class IssueMapper [MapProperty(nameof(Issue.PhoneNumber), nameof(NewIssueAppResponseViewModel.Phone))] public partial NewIssueAppResponseViewModel ToNewIssueAppResponseViewModel(Issue src); - public IssueWithResponse ToIssueWithResponse(Issue src) - { - return new IssueWithResponse - { - Id = src.Id, - ConferenceId = src.ConferenceId, - Message = src.Message, - Resolution = src.Resolution, - Name = src.Name, - Email = src.Email, - PhoneNumber = src.PhoneNumber, - ResponseMarkup = "", - }; - } + [MapperIgnoreSource(nameof(Issue.CreatedAt))] + [MapValue(nameof(IssueWithResponse.ResponseMarkup), "")] + public partial IssueWithResponse ToIssueWithResponse(Issue src); } diff --git a/backend/src/MethodConf.Cms/Mapping/ScheduleMapper.cs b/backend/src/MethodConf.Cms/Mapping/ScheduleMapper.cs index 7267d01..2b22bdd 100644 --- a/backend/src/MethodConf.Cms/Mapping/ScheduleMapper.cs +++ b/backend/src/MethodConf.Cms/Mapping/ScheduleMapper.cs @@ -21,13 +21,13 @@ public ScheduleMapper(IDocumentUrlService documentUrlService) [MapProperty(nameof(Session.Key), nameof(SessionItem.Key), Use = nameof(ResolveSessionKey))] public partial SessionItem ToSessionItem(Session src); - public TrackItem ToTrackItem(Track src) + [MapPropertyFromSource(nameof(TrackItem.Sessions), Use = nameof(MapTrackSessions))] + public partial TrackItem ToTrackItem(Track src); + + private SessionItem[] MapTrackSessions(Track src) { var sessions = src.Children() ?? []; - return new TrackItem - { - Sessions = sessions.Select(ToSessionItem).ToArray() - }; + return sessions.Select(ToSessionItem).ToArray(); } private string ResolveSessionKey(Guid key) => diff --git a/backend/src/MethodConf.Cms/Mapping/SessionFeedbackMapper.cs b/backend/src/MethodConf.Cms/Mapping/SessionFeedbackMapper.cs index 6720286..f0cd526 100644 --- a/backend/src/MethodConf.Cms/Mapping/SessionFeedbackMapper.cs +++ b/backend/src/MethodConf.Cms/Mapping/SessionFeedbackMapper.cs @@ -14,20 +14,12 @@ public partial class SessionFeedbackMapper [MapperIgnoreSource(nameof(SessionFeedback.CreatedAt))] public partial SessionFeedbackResponseDto ToDto(SessionFeedback src); - public SessionFeedback ToSessionFeedback(CreateSessionFeedback src) - { - return new SessionFeedback - { - Id = Guid.Empty, - SessionId = Guid.Empty, - SpeakerRating = src.SpeakerRating, - ContentRating = src.ContentRating, - VenueRating = src.VenueRating, - Comments = src.Comments, - Name = src.Name, - Email = src.Email, - }; - } + [MapValue(nameof(SessionFeedback.Id), Use = nameof(EmptyGuid))] + [MapValue(nameof(SessionFeedback.SessionId), Use = nameof(EmptyGuid))] + [MapperIgnoreTarget(nameof(SessionFeedback.CreatedAt))] + public partial SessionFeedback ToSessionFeedback(CreateSessionFeedback src); private static string NullToEmpty(string? value) => value ?? ""; + + private static Guid EmptyGuid() => Guid.Empty; } From 44887d9ed044ed84fe9c0665d58d8ccb976efbb1 Mon Sep 17 00:00:00 2001 From: Levi Zitting Date: Tue, 7 Jul 2026 10:56:17 -0500 Subject: [PATCH 3/6] refactor: remove unnecessary NullToEmpty helper, tighten ToDto mapping strategy --- backend/src/MethodConf.Cms/Mapping/ScheduleMapper.cs | 1 + backend/src/MethodConf.Cms/Mapping/SessionFeedbackMapper.cs | 3 --- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/backend/src/MethodConf.Cms/Mapping/ScheduleMapper.cs b/backend/src/MethodConf.Cms/Mapping/ScheduleMapper.cs index 2b22bdd..bbb58ac 100644 --- a/backend/src/MethodConf.Cms/Mapping/ScheduleMapper.cs +++ b/backend/src/MethodConf.Cms/Mapping/ScheduleMapper.cs @@ -16,6 +16,7 @@ public ScheduleMapper(IDocumentUrlService documentUrlService) _documentUrlService = documentUrlService; } + [MapperRequiredMapping(RequiredMappingStrategy.Both)] public partial ConferenceScheduleResponseDto ToDto(ConferenceSchedule src); [MapProperty(nameof(Session.Key), nameof(SessionItem.Key), Use = nameof(ResolveSessionKey))] diff --git a/backend/src/MethodConf.Cms/Mapping/SessionFeedbackMapper.cs b/backend/src/MethodConf.Cms/Mapping/SessionFeedbackMapper.cs index f0cd526..7052460 100644 --- a/backend/src/MethodConf.Cms/Mapping/SessionFeedbackMapper.cs +++ b/backend/src/MethodConf.Cms/Mapping/SessionFeedbackMapper.cs @@ -7,7 +7,6 @@ namespace MethodConf.Cms.Mapping; [Mapper] public partial class SessionFeedbackMapper { - [MapProperty(nameof(CreateSessionFeedbackRequestDto.Comments), nameof(CreateSessionFeedback.Comments), Use = nameof(NullToEmpty))] public partial CreateSessionFeedback ToCreateSessionFeedback(CreateSessionFeedbackRequestDto src); [MapperIgnoreSource(nameof(SessionFeedback.SessionId))] @@ -19,7 +18,5 @@ public partial class SessionFeedbackMapper [MapperIgnoreTarget(nameof(SessionFeedback.CreatedAt))] public partial SessionFeedback ToSessionFeedback(CreateSessionFeedback src); - private static string NullToEmpty(string? value) => value ?? ""; - private static Guid EmptyGuid() => Guid.Empty; } From b76fcb3a0e46906ac238a9a3adec13d343b20e70 Mon Sep 17 00:00:00 2001 From: Levi Zitting Date: Tue, 7 Jul 2026 11:01:51 -0500 Subject: [PATCH 4/6] refactor: use primary constructor for schedule mapper --- backend/src/MethodConf.Cms/Mapping/ScheduleMapper.cs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/backend/src/MethodConf.Cms/Mapping/ScheduleMapper.cs b/backend/src/MethodConf.Cms/Mapping/ScheduleMapper.cs index bbb58ac..5749f5c 100644 --- a/backend/src/MethodConf.Cms/Mapping/ScheduleMapper.cs +++ b/backend/src/MethodConf.Cms/Mapping/ScheduleMapper.cs @@ -7,15 +7,8 @@ namespace MethodConf.Cms.Mapping; [Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)] -public partial class ScheduleMapper +public partial class ScheduleMapper(IDocumentUrlService documentUrlService) { - private readonly IDocumentUrlService _documentUrlService; - - public ScheduleMapper(IDocumentUrlService documentUrlService) - { - _documentUrlService = documentUrlService; - } - [MapperRequiredMapping(RequiredMappingStrategy.Both)] public partial ConferenceScheduleResponseDto ToDto(ConferenceSchedule src); @@ -32,5 +25,5 @@ private SessionItem[] MapTrackSessions(Track src) } private string ResolveSessionKey(Guid key) => - _documentUrlService.GetUrlSegment(key, "", false) ?? key.ToString(); + documentUrlService.GetUrlSegment(key, "", false) ?? key.ToString(); } From 4a1531b5b219f62ce1f5852544b66551e823732c Mon Sep 17 00:00:00 2001 From: Levi Zitting Date: Tue, 7 Jul 2026 11:05:45 -0500 Subject: [PATCH 5/6] refactor: pass sessionId as mapping parameter instead of post-map mutation --- backend/src/MethodConf.Cms/Mapping/SessionFeedbackMapper.cs | 3 +-- backend/src/MethodConf.Cms/Services/SessionFeedbackService.cs | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/backend/src/MethodConf.Cms/Mapping/SessionFeedbackMapper.cs b/backend/src/MethodConf.Cms/Mapping/SessionFeedbackMapper.cs index 7052460..765d27a 100644 --- a/backend/src/MethodConf.Cms/Mapping/SessionFeedbackMapper.cs +++ b/backend/src/MethodConf.Cms/Mapping/SessionFeedbackMapper.cs @@ -14,9 +14,8 @@ public partial class SessionFeedbackMapper public partial SessionFeedbackResponseDto ToDto(SessionFeedback src); [MapValue(nameof(SessionFeedback.Id), Use = nameof(EmptyGuid))] - [MapValue(nameof(SessionFeedback.SessionId), Use = nameof(EmptyGuid))] [MapperIgnoreTarget(nameof(SessionFeedback.CreatedAt))] - public partial SessionFeedback ToSessionFeedback(CreateSessionFeedback src); + public partial SessionFeedback ToSessionFeedback(CreateSessionFeedback src, Guid sessionId); private static Guid EmptyGuid() => Guid.Empty; } diff --git a/backend/src/MethodConf.Cms/Services/SessionFeedbackService.cs b/backend/src/MethodConf.Cms/Services/SessionFeedbackService.cs index 360e6fe..5d71127 100644 --- a/backend/src/MethodConf.Cms/Services/SessionFeedbackService.cs +++ b/backend/src/MethodConf.Cms/Services/SessionFeedbackService.cs @@ -24,8 +24,7 @@ public async Task> Create(Guid sessionId, CreateSessionF return new IneligibleForFeedback(session); } - var sessionFeedback = mapper.ToSessionFeedback(createSessionFeedback); - sessionFeedback.SessionId = session.Key; + var sessionFeedback = mapper.ToSessionFeedback(createSessionFeedback, session.Key); sessionFeedback.CreatedAt = DateTime.UtcNow; From 790cea054bb35594fec8ab41f4285587d1a17082 Mon Sep 17 00:00:00 2001 From: Levi Zitting Date: Tue, 7 Jul 2026 11:06:42 -0500 Subject: [PATCH 6/6] refactor: pass conferenceId as mapping parameter instead of post-map mutation --- backend/src/MethodConf.Cms/Mapping/IssueMapper.cs | 3 +-- backend/src/MethodConf.Cms/Services/ConferenceIssueService.cs | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/backend/src/MethodConf.Cms/Mapping/IssueMapper.cs b/backend/src/MethodConf.Cms/Mapping/IssueMapper.cs index b2d5d69..50477e5 100644 --- a/backend/src/MethodConf.Cms/Mapping/IssueMapper.cs +++ b/backend/src/MethodConf.Cms/Mapping/IssueMapper.cs @@ -11,9 +11,8 @@ public partial class IssueMapper public partial CreateIssue ToCreateIssue(CreateIssueRequestDto src); [MapperIgnoreTarget(nameof(Issue.Id))] - [MapperIgnoreTarget(nameof(Issue.ConferenceId))] [MapperIgnoreTarget(nameof(Issue.CreatedAt))] - public partial Issue ToIssue(CreateIssue src); + public partial Issue ToIssue(CreateIssue src, Guid conferenceId); [MapperIgnoreSource(nameof(IssueWithResponse.Id))] [MapperIgnoreSource(nameof(IssueWithResponse.ConferenceId))] diff --git a/backend/src/MethodConf.Cms/Services/ConferenceIssueService.cs b/backend/src/MethodConf.Cms/Services/ConferenceIssueService.cs index cf79aa9..7c3e5fa 100644 --- a/backend/src/MethodConf.Cms/Services/ConferenceIssueService.cs +++ b/backend/src/MethodConf.Cms/Services/ConferenceIssueService.cs @@ -33,8 +33,7 @@ public async Task> CreateIssue(Guid conferenceId, Crea return Result.Fail(new InvalidEntityIdError(conferenceId.ToString())); } - var newIssue = mapper.ToIssue(createIssue); - newIssue.ConferenceId = conference.Key; + var newIssue = mapper.ToIssue(createIssue, conference.Key); newIssue.CreatedAt = DateTime.UtcNow; dbContext.Add(newIssue);