fix: offload building DTOs to GUHF

DTO building allows for fully returning correct event's
skills and registrations
This commit is contained in:
2025-06-01 20:33:58 +02:00
parent 426288d728
commit efb71b24d3
10 changed files with 182 additions and 72 deletions

View File

@@ -1,18 +1,21 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using WebApp.Entities; using WebApp.Entities;
namespace WebApp.DTOs; namespace WebApp.DTOs;
// Output values in JSON file // Output values in JSON file
public record class EventDetailsDto public record class EventDetailsDto
( {
int EventId, public int EventId { get; set; }
[Required] int? OrganisationId, [Required] public int? OrganisationId { get; set; }
[Required] string? OrganisationName, [Required] public string? OrganisationName { get; set; }
[Required][StringLength(50)] string Title, [Required][StringLength(50)] public string Title { get; set; }
[StringLength(500)] string Description, [StringLength(500)] public string Description { get; set; }
[Required][StringLength(100)] string Location, [Required][StringLength(100)] public string Location { get; set; }
[Required] DateTime? EventDate, [Required] public DateTime? EventDate { get; set; }
ICollection<EventSkill> EventSkills, //ICollection<EventSkill> EventSkills,
ICollection<EventRegistration> EventRegistrations public ICollection<SkillSummaryDto> EventSkills { get; set; }
); public ICollection<EventRegistrationDto> EventRegistrations { get; set; }
public EventDetailsDto() { }
};

View File

@@ -0,0 +1,15 @@
using System.ComponentModel.DataAnnotations;
using WebApp.Entities;
namespace WebApp.DTOs;
public record class EventRegistrationDto
{
public int EventId { get; set; }
public int UserId { get; set; }
public string UserName { get; set; }
public DateTime RegisteredAt { get; set; }
public EventRegistrationDto() { }
};

View File

@@ -1,17 +1,19 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using WebApp.Entities; using WebApp.Entities;
namespace WebApp.DTOs; namespace WebApp.DTOs;
// Output values in JSON file // Output values in JSON file
public record class EventSummaryDto( public record class EventSummaryDto {
int EventId, public int EventId { get; set; }
[Required] string Organisation, [Required] public string Organisation { get; set; }
[Required] int OrganisationId, [Required] public int OrganisationId { get; set; }
[Required] [StringLength(50)] string Title, [Required] [StringLength(50)] public string Title { get; set; }
[StringLength(500)] string Description, [StringLength(500)] public string Description { get; set; }
[Required] [StringLength(100)] string Location, [Required] [StringLength(100)] public string Location { get; set; }
[Required] DateTime? EventDate, [Required] public DateTime? EventDate { get; set; }
ICollection<EventSkill> EventSkills, public ICollection<EventSkill> EventSkills { get; set; }
ICollection<EventRegistration> EventRegistrations public ICollection<EventRegistration> EventRegistrations { get; set; }
);
};

View File

@@ -1,4 +1,4 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using WebApp.Entities; using WebApp.Entities;
namespace WebApp.DTOs; namespace WebApp.DTOs;
@@ -13,4 +13,5 @@ public record class EventSummaryNoErDto(
[Required][StringLength(100)] string Location, [Required][StringLength(100)] string Location,
[Required] DateTime? EventDate, [Required] DateTime? EventDate,
ICollection<EventSkill> EventSkills ICollection<EventSkill> EventSkills
// ICollection<SkillSummaryDto> EventSkills
); );

View File

@@ -1,9 +1,13 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using WebApp.Entities;
namespace WebApp.DTOs; namespace WebApp.DTOs;
public record class SkillSummaryDto public record class SkillSummaryDto
( {
[Required] int SkillId, public int? SkillId { get; set; }
[Required] string SkillName public string? SkillName { get; set; }
);
public SkillSummaryDto() { }
};

View File

@@ -1,5 +1,7 @@
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.Intrinsics.Arm;
using WebApp.Data; using WebApp.Data;
using WebApp.DTOs; using WebApp.DTOs;
using WebApp.Entities; using WebApp.Entities;
@@ -46,8 +48,11 @@ namespace WebApp.Endpoints
group.MapGet("/{id}", group.MapGet("/{id}",
async (int id, ApplicationDbContext dbContext, HttpContext httpContext, GeneralUseHelpers guhf) => async (int id, ApplicationDbContext dbContext, HttpContext httpContext, GeneralUseHelpers guhf) =>
{ {
Event? Eve = await dbContext.Events.FindAsync(id);
Event? Eve = await dbContext
.Events
.Include(e => e.Organisation)
.FirstOrDefaultAsync(e => e.EventId == id);
if (Eve is null) return Results.NotFound(); if (Eve is null) return Results.NotFound();
// Sprawdź, czy token należy do organizacji, a jeżeli tak, to do której. // Sprawdź, czy token należy do organizacji, a jeżeli tak, to do której.
@@ -55,16 +60,14 @@ namespace WebApp.Endpoints
Organisation? org = await guhf.GetOrganisationFromToken(token); Organisation? org = await guhf.GetOrganisationFromToken(token);
// Jeśli token należy do organizacji, która utworzyła to wydarzenie, // Jeśli token należy do organizacji, która utworzyła to wydarzenie,
// to zwróć także EventRegistrations. W przeciwnym razie usuń to pole // to zwróć także EventRegistrations. W przeciwnym razie niech będzie to
// przed jego wysłaniem! // puste pole.
if (org is null || org.OrganisationId != Eve.OrganisationId) Eve.EventRegistrations = []; List<EventDetailsDto> result = await guhf.BuildDetailedEventsDto(
dbContext,
(org is not null && Eve.Organisation == org)
);
// DLACZEGO? return Results.Ok(result.FirstOrDefault(e => e.EventId == id));
Eve.Organisation = await guhf.GetOrganisationFromId(Eve.OrganisationId);
EventDetailsDto EveDto = Eve.ToEventDetailsDto();
return Results.Ok(EveDto); //EventDetailsDto
}) })
.WithName(GetEventEndpointName); .WithName(GetEventEndpointName);
@@ -214,6 +217,9 @@ namespace WebApp.Endpoints
// UWAGA! TO NIE POWINNO TAK DZIAŁAĆ! // UWAGA! TO NIE POWINNO TAK DZIAŁAĆ!
// KTOKOLWIEK WIDZIAŁ, KTOKOLWIEK WIE CZEMU Organisation JEST null? // KTOKOLWIEK WIDZIAŁ, KTOKOLWIEK WIE CZEMU Organisation JEST null?
//
// Odpowiedź: Bo pobieramy dane bez .Include(e => e.Organisation),
// co zapobiega masie innych problemów, m.in. rekurencyjnym importom.
e.Organisation = await guhf.GetOrganisationFromId(e.OrganisationId); e.Organisation = await guhf.GetOrganisationFromId(e.OrganisationId);
if (matchFound) SearchResults.Add(e.ToEventSummaryDto()); if (matchFound) SearchResults.Add(e.ToEventSummaryDto());

View File

@@ -1,5 +1,6 @@
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using WebApp.Data; using WebApp.Data;
using WebApp.DTOs;
using WebApp.Entities; using WebApp.Entities;
namespace WebApp.Endpoints; namespace WebApp.Endpoints;
@@ -112,4 +113,49 @@ public class GeneralUseHelpers
// Sprawdza, czy któreś ze słów pasuje (nawet częściowo) do searchTerm // Sprawdza, czy któreś ze słów pasuje (nawet częściowo) do searchTerm
return words.Any(word => word.Contains(searchTerm, StringComparison.OrdinalIgnoreCase)); return words.Any(word => word.Contains(searchTerm, StringComparison.OrdinalIgnoreCase));
} }
public async Task<List<EventDetailsDto>> BuildDetailedEventsDto(ApplicationDbContext context, bool includeEventRegistrations = false)
{
// https://khalidabuhakmeh.com/ef-core-and-aspnet-core-cycle-issue-and-solution
// Jeśli token należy do organizacji, która utworzyła to wydarzenie,
// to zwróć także EventRegistrations. W przeciwnym razie niech będzie to
// puste pole.
ICollection<EventRegistrationDto> ERs = new List<EventRegistrationDto>();
if (includeEventRegistrations)
{
ERs = await context
.EventRegistrations
.Select(er => new EventRegistrationDto
{
EventId = er.EventId,
UserId = er.UserId,
UserName = er.User.FirstName + " " + er.User.LastName,
RegisteredAt = er.RegisteredAt
}).ToListAsync();
}
List<EventDetailsDto> result = await context
.Events
.Select(e => new EventDetailsDto
{
EventId = e.EventId,
OrganisationId = e.OrganisationId,
OrganisationName = e.Organisation.Name,
Title = e.Title,
Description = e.Description,
Location = e.Location,
EventDate = e.EventDate,
EventSkills = e
.EventSkills
.Select(es => new SkillSummaryDto
{
SkillId = es.SkillId,
SkillName = es.Skill.Name
}).ToList(),
EventRegistrations = ERs
}).ToListAsync();
return result;
}
} }

View File

@@ -1,4 +1,4 @@
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using WebApp.DTOs; using WebApp.DTOs;
using WebApp.Entities; using WebApp.Entities;
@@ -34,20 +34,21 @@ public static class EventMapping
public static EventSummaryDto ToEventSummaryDto(this Event myEvent) public static EventSummaryDto ToEventSummaryDto(this Event myEvent)
{ {
return new EventSummaryDto( return new EventSummaryDto {
myEvent.EventId, EventId = myEvent.EventId,
myEvent.Organisation!.Name, Organisation = myEvent.Organisation!.Name,
myEvent.OrganisationId, OrganisationId = myEvent.OrganisationId,
myEvent.Title, Title = myEvent.Title,
myEvent.Description, Description = myEvent.Description,
myEvent.Location, Location = myEvent.Location,
myEvent.EventDate, EventDate = myEvent.EventDate,
myEvent.EventSkills, EventSkills = myEvent.EventSkills,
myEvent.EventRegistrations EventRegistrations = myEvent.EventRegistrations
); };
} }
public static EventSummaryNoErDto ToEventSummaryNoErDto(this Event myEvent) public static EventSummaryNoErDto ToEventSummaryNoErDto(this Event myEvent)
{ {
return new EventSummaryNoErDto( return new EventSummaryNoErDto(
myEvent.EventId, myEvent.EventId,
myEvent.Organisation!.Name, myEvent.Organisation!.Name,
@@ -60,18 +61,42 @@ public static class EventMapping
); );
} }
public static EventRegistrationDto ToEventRegistrationDto(this EventRegistration myER)
{
return new EventRegistrationDto {
EventId = myER.EventId,
UserId = myER.UserId,
UserName = myER.User.FirstName + " " + myER.User.LastName,
RegisteredAt = myER.RegisteredAt
};
}
public static EventDetailsDto ToEventDetailsDto(this Event myEvent) public static EventDetailsDto ToEventDetailsDto(this Event myEvent)
{ {
return new EventDetailsDto( List<SkillSummaryDto> ssdto = new List<SkillSummaryDto>();
myEvent.EventId, List<EventRegistrationDto> erdto = new List<EventRegistrationDto>();
myEvent.OrganisationId,
myEvent.Organisation.Name, foreach (EventSkill es in myEvent.EventSkills)
myEvent.Title, {
myEvent.Description, ssdto.Add(es.ToSkillSummaryDto());
myEvent.Location, }
myEvent.EventDate,
myEvent.EventSkills, foreach (EventRegistration er in myEvent.EventRegistrations)
myEvent.EventRegistrations {
); erdto.Add(er.ToEventRegistrationDto());
}
return new EventDetailsDto {
EventId = myEvent.EventId,
OrganisationId = myEvent.OrganisationId,
OrganisationName = myEvent.Organisation.Name,
Title = myEvent.Title,
Description = myEvent.Description,
Location = myEvent.Location,
EventDate = myEvent.EventDate,
EventSkills = ssdto,
EventRegistrations = erdto
};
} }
} }

View File

@@ -13,4 +13,12 @@ public static class EventSkillMapping
SkillId = SSDto.Skill, SkillId = SSDto.Skill,
}; };
} }
public static SkillSummaryDto ToSkillSummaryDto(this EventSkill es)
{
return new SkillSummaryDto{
SkillId = es.SkillId,
SkillName = es.Skill.Name
};
}
} }

View File

@@ -1,4 +1,4 @@
using WebApp.DTOs; using WebApp.DTOs;
using WebApp.Entities; using WebApp.Entities;
namespace WebApp.Mapping namespace WebApp.Mapping
@@ -16,10 +16,10 @@ namespace WebApp.Mapping
public static SkillSummaryDto ToSkillSummaryDto(this Skill s) public static SkillSummaryDto ToSkillSummaryDto(this Skill s)
{ {
return new SkillSummaryDto( return new SkillSummaryDto {
s.SkillId, SkillId = s.SkillId,
s.Name SkillName = s.Name
); };
} }
} }
} }