diff --git a/src/ALB.Api/Endpoints/AttendanceList/AttendanceListEntries/CreateAttendanceListEntryEndpoint.cs b/src/ALB.Api/Endpoints/AttendanceList/AttendanceListEntries/CreateAttendanceListEntryEndpoint.cs index b4fd32f..830fa4a 100644 --- a/src/ALB.Api/Endpoints/AttendanceList/AttendanceListEntries/CreateAttendanceListEntryEndpoint.cs +++ b/src/ALB.Api/Endpoints/AttendanceList/AttendanceListEntries/CreateAttendanceListEntryEndpoint.cs @@ -1,6 +1,7 @@ using ALB.Api.Extensions; -using ALB.Domain.Enum; +using ALB.Domain.Entities; using ALB.Domain.Repositories; +using ALB.Domain.Values; using FastEndpoints; using NodaTime; @@ -11,25 +12,55 @@ public class CreateAttendanceListEntryEndpoint(IAttendanceRepository repository) { public override void Configure() { - Post("/api/attendance-lists/entries"); - AllowAnonymous(); + Post("/api/attendance-lists/{attendanceListId:guid}/entries"); + Policies(SystemRoles.AdminPolicy); } public override async Task HandleAsync(CreateAttendanceListEntryRequest request, CancellationToken ct) { - await repository.CreateAsync(request.ChildId, LocalDate.FromDateTime(request.Date), - request.ArrivalAt.ToNodaLocalTime(), request.DepartureAt.ToNodaLocalTime(), request.Status, ct); + var attendanceListId = Route("attendanceListId"); + var date = LocalDate.FromDateTime(request.Date); + + var exists = await repository.ExistsAsync( + attendanceListId, + request.ChildId, + date, + ct); + + if (exists) + { + AddError("An attendance entry already exists for this child on this date."); + await SendErrorsAsync(400, ct); + return; + } - await SendAsync(new CreateAttendanceListEntryResponse( - $"Attendance for {request.ChildId} at {LocalDate.FromDateTime(request.Date)} was successfully set to {request.Status}")); + var entry = new AttendanceListEntry + { + Id = Guid.NewGuid(), + AttendanceListId = attendanceListId, + ChildId = request.ChildId, + Date = date, + ArrivalAt = request.ArrivalAt?.ToNodaLocalTime(), + DepartureAt = request.DepartureAt?.ToNodaLocalTime(), + AttendanceStatusId = request.AttendanceStatusId + }; + + var created = await repository.CreateAsync(entry, ct); + + await SendAsync( + new CreateAttendanceListEntryResponse( + created.Id, + $"Attendance entry for child {request.ChildId} on {request.Date:yyyy-MM-dd} was successfully created."), + 200, + ct); } } public record CreateAttendanceListEntryRequest( Guid ChildId, DateTime Date, - DateTime ArrivalAt, - DateTime DepartureAt, - AttendanceStatus Status); + DateTime? ArrivalAt, + DateTime? DepartureAt, + int AttendanceStatusId); -public record CreateAttendanceListEntryResponse(string Message); \ No newline at end of file +public record CreateAttendanceListEntryResponse(Guid Id, string Message); \ No newline at end of file diff --git a/src/ALB.Api/Endpoints/AttendanceList/AttendanceListEntries/DeleteAttendanceListEntryEndpoint.cs b/src/ALB.Api/Endpoints/AttendanceList/AttendanceListEntries/DeleteAttendanceListEntryEndpoint.cs index 56aa179..6cffd3f 100644 --- a/src/ALB.Api/Endpoints/AttendanceList/AttendanceListEntries/DeleteAttendanceListEntryEndpoint.cs +++ b/src/ALB.Api/Endpoints/AttendanceList/AttendanceListEntries/DeleteAttendanceListEntryEndpoint.cs @@ -1,4 +1,5 @@ using ALB.Domain.Repositories; +using ALB.Domain.Values; using FastEndpoints; using NodaTime; @@ -9,16 +10,27 @@ public class DeleteAttendanceListEntryEndpoint(IAttendanceRepository repository) { public override void Configure() { - Delete("/api/attendance-lists/entries"); - AllowAnonymous(); + Delete("/api/attendance-lists/{attendanceListId:guid}/entries"); + Policies(SystemRoles.AdminPolicy); } public override async Task HandleAsync(DeleteAttendanceListEntryRequest request, CancellationToken ct) { - await repository.DeleteAsync(request.ChildId, LocalDate.FromDateTime(request.Date), ct); + var attendanceListId = Route("attendanceListId"); + var date = LocalDate.FromDateTime(request.Date); + + var attendanceListEntry = await repository.GetByListChildAndDateAsync(attendanceListId, request.ChildId, date, ct); + + if (attendanceListEntry is null) + { + await SendNotFoundAsync(ct); + return; + } + + await repository.DeleteAsync(attendanceListEntry.Id, ct); await SendAsync(new DeleteAttendanceListEntryResponse( - $"Attendance for {request.ChildId} at {request.Date} was successfully deleted.")); + $"Attendance for {request.ChildId} at {request.Date} was successfully deleted."), cancellation: ct); } } diff --git a/src/ALB.Api/Endpoints/AttendanceList/AttendanceListEntries/GetAttendancesOfChildEndpoint.cs b/src/ALB.Api/Endpoints/AttendanceList/AttendanceListEntries/GetAttendancesOfChildEndpoint.cs new file mode 100644 index 0000000..310e5ed --- /dev/null +++ b/src/ALB.Api/Endpoints/AttendanceList/AttendanceListEntries/GetAttendancesOfChildEndpoint.cs @@ -0,0 +1,28 @@ +using ALB.Api.Extensions; +using ALB.Api.Models; +using ALB.Domain.Repositories; +using FastEndpoints; + +namespace ALB.Api.Endpoints.AttendanceList.AttendanceListEntries; + +public class GetAttendancesOfAttendanceListEndpoint(IAttendanceListRepository repository) : EndpointWithoutRequest +{ + public override void Configure() + { + Get("/api/attendance-lists/{attendanceListId:guid}/entries"); + Policies("AdminPolicy"); + } + + public override async Task HandleAsync(CancellationToken ct) + { + var attendanceListId = Route("attendanceListId"); + + var attendances = (await repository.GetAttendancesOfAttendanceListAsync(attendanceListId, ct)) + .Select(g => g.ToDto()) + .ToList(); + + await SendAsync(new GetAttendancesOfAttendanceListResponse(attendances), cancellation: ct); + } +} + +public record GetAttendancesOfAttendanceListResponse(IEnumerable AttendanceListEntries); \ No newline at end of file diff --git a/src/ALB.Api/Endpoints/AttendanceList/AttendanceListEntries/UpdateAttendanceListEntryEndpoint.cs b/src/ALB.Api/Endpoints/AttendanceList/AttendanceListEntries/UpdateAttendanceListEntryEndpoint.cs index 0ef9222..1f8cb01 100644 --- a/src/ALB.Api/Endpoints/AttendanceList/AttendanceListEntries/UpdateAttendanceListEntryEndpoint.cs +++ b/src/ALB.Api/Endpoints/AttendanceList/AttendanceListEntries/UpdateAttendanceListEntryEndpoint.cs @@ -1,6 +1,7 @@ using ALB.Api.Extensions; using ALB.Domain.Enum; using ALB.Domain.Repositories; +using ALB.Domain.Values; using FastEndpoints; using NodaTime; @@ -12,16 +13,30 @@ public class UpdateAttendanceListEntryEndpoint(IAttendanceRepository repository) public override void Configure() { Put("/api/attendance-lists/entries"); - AllowAnonymous(); + Policies(SystemRoles.AdminPolicy); } public override async Task HandleAsync(UpdateAttendanceListEntryRequest request, CancellationToken ct) { - await repository.UpdateAsync(request.ChildId, LocalDate.FromDateTime(request.Date), - request.ArrivalAt.ToNodaLocalTime(), request.DepartureAt.ToNodaLocalTime(), request.Status, ct); + var attendanceListId = Route("attendanceListId"); + var date = LocalDate.FromDateTime(request.Date); + + var attendanceListEntry = await repository.GetByListChildAndDateAsync(attendanceListId, request.ChildId, date, ct); + + if (attendanceListEntry is null) + { + await SendNotFoundAsync(ct); + return; + } + + attendanceListEntry.ArrivalAt = request.ArrivalAt.ToNodaLocalTime(); + attendanceListEntry.DepartureAt = request.DepartureAt.ToNodaLocalTime(); + attendanceListEntry.AttendanceStatus = request.Status; + + await repository.UpdateAsync(attendanceListEntry, ct); await SendAsync(new UpdateAttendanceListEntryResponse( - $"Attendance for {request.ChildId} at {LocalDate.FromDateTime(request.Date)} was successfully set to {request.Status}")); + $"Attendance for {request.ChildId} at {request.Date} was successfully updated."), cancellation: ct); } } diff --git a/src/ALB.Api/Endpoints/AttendanceList/CreateAttandancePageEndpoint.cs b/src/ALB.Api/Endpoints/AttendanceList/CreateAttandancePageEndpoint.cs deleted file mode 100644 index 5538225..0000000 --- a/src/ALB.Api/Endpoints/AttendanceList/CreateAttandancePageEndpoint.cs +++ /dev/null @@ -1,49 +0,0 @@ -using ALB.Domain.Repositories; -using ALB.Domain.Values; -using FastEndpoints; -using NodaTime; - -namespace ALB.Api.Endpoints.AttendanceList; - -public class GetAttendancePageEndpoint( - IAttendanceRepository attendanceListRepo, - IChildRepository childRepo, - IAbsenceDayRepository absenceRepo -) : Endpoint -{ - public override void Configure() - { - Get("/api/attendancelists/{AttendanceListId}/page"); - Policies(SystemRoles.TeamPolicy); - } - - public override async Task HandleAsync(GetAttendancePageRequest request, CancellationToken ct) - { - var attendanceList = await attendanceListRepo.GetAttendanceListByIdAsync(request.AttendanceListId); - - if (attendanceList is null) - { - await SendNotFoundAsync(ct); - return; - } - - var children = await childRepo.GetByCohortAsync(attendanceList.CohortId); - - var absences = await absenceRepo.GetByDateAsync(request.date); - - var dtos = children.Select(child => - { - var absence = absences.FirstOrDefault(a => a.ChildId == child.Id); - var status = absence?.AbsenceStatus.Name; - return new AttendancePageChildDto(child.Id, child.FirstName, child.LastName, status); - }).ToList(); - - await SendAsync(new GetAttendancePageResponse(dtos), cancellation: ct); - } -} - -public record GetAttendancePageRequest(Guid AttendanceListId, LocalDate date); - -public record AttendancePageChildDto(Guid ChildId, string FirstName, string LastName, string Status); - -public record GetAttendancePageResponse(List Children); \ No newline at end of file diff --git a/src/ALB.Api/Endpoints/AttendanceList/CreateAttendanceListEndpoint.cs b/src/ALB.Api/Endpoints/AttendanceList/CreateAttendanceListEndpoint.cs new file mode 100644 index 0000000..0672f5f --- /dev/null +++ b/src/ALB.Api/Endpoints/AttendanceList/CreateAttendanceListEndpoint.cs @@ -0,0 +1,45 @@ +using ALB.Domain.Repositories; +using ALB.Domain.Values; +using FastEndpoints; +using NodaTime; + +namespace ALB.Api.Endpoints.AttendanceList; + +public class CreateAttendanceListEndpoint(IAttendanceListRepository attendanceListRepository) + : Endpoint +{ + public override void Configure() + { + Post("/api/attendance-lists"); + Policies(SystemRoles.AdminPolicy); + } + + public override async Task HandleAsync(CreateAttendanceListRequest request, CancellationToken ct) + { + var attendanceList = new Domain.Entities.AttendanceList + { + CohortId = request.CohortId, + Open = request.Open, + ValidationPeriod = new DateInterval( + LocalDate.FromDateTime(request.ValidationStart), + LocalDate.FromDateTime(request.ValidationEnd) + ) + }; + + var createdList = await attendanceListRepository.CreateAsync(attendanceList, ct); + + await SendAsync( + new CreateAttendanceListResponse(createdList.Id, "Attendance list created successfully."), + cancellation: ct + ); + } +} + +public record CreateAttendanceListRequest( + Guid CohortId, + bool Open, + DateTime ValidationStart, + DateTime ValidationEnd +); + +public record CreateAttendanceListResponse(Guid Id, string Message); \ No newline at end of file diff --git a/src/ALB.Api/Endpoints/AttendanceList/DeleteAttendanceListEndpoint.cs b/src/ALB.Api/Endpoints/AttendanceList/DeleteAttendanceListEndpoint.cs new file mode 100644 index 0000000..512e375 --- /dev/null +++ b/src/ALB.Api/Endpoints/AttendanceList/DeleteAttendanceListEndpoint.cs @@ -0,0 +1,32 @@ +using ALB.Domain.Repositories; +using ALB.Domain.Values; +using FastEndpoints; + +namespace ALB.Api.Endpoints.AttendanceList; + +public class DeleteAttendanceListEndpoint(IAttendanceListRepository attendanceListRepository) + : EndpointWithoutRequest +{ + public override void Configure() + { + Delete("/api/attendance-lists/{attendanceListId:guid}"); + Policies(SystemRoles.AdminPolicy); + } + + public override async Task HandleAsync(CancellationToken ct) + { + var attendanceListId = Route("attendanceListId"); + + var attendanceList = await attendanceListRepository.GetByIdAsync(attendanceListId, ct); + + if (attendanceList is null) + { + await SendNotFoundAsync(ct); + return; + } + + await attendanceListRepository.DeleteAsync(attendanceListId, ct); + + await SendNoContentAsync(ct); + } +} \ No newline at end of file diff --git a/src/ALB.Api/Endpoints/AttendanceList/GetAttendanceListEndpoint.cs b/src/ALB.Api/Endpoints/AttendanceList/GetAttendanceListEndpoint.cs new file mode 100644 index 0000000..25cdf56 --- /dev/null +++ b/src/ALB.Api/Endpoints/AttendanceList/GetAttendanceListEndpoint.cs @@ -0,0 +1,47 @@ +using ALB.Domain.Repositories; +using ALB.Domain.Values; +using FastEndpoints; +using NodaTime; + +namespace ALB.Api.Endpoints.AttendanceList; + +public class GetAttendanceListEndpoint(IAttendanceListRepository attendanceListRepository) + : EndpointWithoutRequest +{ + public override void Configure() + { + Get("/api/attendance-lists/{attendanceListId:guid}"); + Policies(SystemRoles.AdminPolicy); + } + + public override async Task HandleAsync(CancellationToken ct) + { + var attendanceListId = Route("attendanceListId"); + + var attendanceList = await attendanceListRepository.GetByIdAsync(attendanceListId, ct); + + if (attendanceList is null) + { + await SendNotFoundAsync(ct); + return; + } + + var response = new GetAttendanceListResponse( + attendanceList.Id, + attendanceList.CohortId, + attendanceList.Open, + attendanceList.ValidationPeriod.Start.ToDateTimeUnspecified(), + attendanceList.ValidationPeriod.End.ToDateTimeUnspecified() + ); + + await SendAsync(response, cancellation: ct); + } +} + +public record GetAttendanceListResponse( + Guid Id, + Guid CohortId, + bool Open, + DateTime ValidationStart, + DateTime ValidationEnd +); \ No newline at end of file diff --git a/src/ALB.Api/Endpoints/AttendanceList/GetAttendanceListsEndpoint.cs b/src/ALB.Api/Endpoints/AttendanceList/GetAttendanceListsEndpoint.cs new file mode 100644 index 0000000..44d7599 --- /dev/null +++ b/src/ALB.Api/Endpoints/AttendanceList/GetAttendanceListsEndpoint.cs @@ -0,0 +1,28 @@ +using ALB.Api.Extensions; +using ALB.Api.Models; +using ALB.Domain.Repositories; +using ALB.Domain.Values; +using FastEndpoints; + +namespace ALB.Api.Endpoints.AttendanceList; + +public class GetAttendanceListsEndpoint(IAttendanceListRepository attendanceListRepository) + : EndpointWithoutRequest +{ + public override void Configure() + { + Get("/api/attendance-lists"); + Policies(SystemRoles.AdminPolicy); + } + + public override async Task HandleAsync(CancellationToken ct) + { + var attendanceLists = await attendanceListRepository.GetAllAsync(ct); + + var dtos = attendanceLists.Select(al => al.ToDto()).ToList(); + + await SendAsync(new GetAttendanceListsResponse(dtos), cancellation: ct); + } +} + +public record GetAttendanceListsResponse(IEnumerable AttendanceLists); \ No newline at end of file diff --git a/src/ALB.Api/Endpoints/AttendanceList/UpdateAttendanceListEndpoint.cs b/src/ALB.Api/Endpoints/AttendanceList/UpdateAttendanceListEndpoint.cs new file mode 100644 index 0000000..870bb61 --- /dev/null +++ b/src/ALB.Api/Endpoints/AttendanceList/UpdateAttendanceListEndpoint.cs @@ -0,0 +1,45 @@ +using ALB.Domain.Repositories; +using ALB.Domain.Values; +using FastEndpoints; +using NodaTime; + +namespace ALB.Api.Endpoints.AttendanceList; + +public class UpdateAttendanceListEndpoint(IAttendanceListRepository attendanceListRepository) + : Endpoint +{ + public override void Configure() + { + Put("/api/attendance-lists/{attendanceListId:guid}"); + Policies(SystemRoles.AdminPolicy); + } + + public override async Task HandleAsync(UpdateAttendanceListRequest request, CancellationToken ct) + { + var attendanceListId = Route("attendanceListId"); + + var existingList = await attendanceListRepository.GetByIdAsync(attendanceListId, ct); + + if (existingList is null) + { + await SendNotFoundAsync(ct); + return; + } + + existingList.Open = request.Open; + existingList.ValidationPeriod = new DateInterval( + LocalDate.FromDateTime(request.ValidationStart), + LocalDate.FromDateTime(request.ValidationEnd) + ); + + await attendanceListRepository.UpdateAsync(existingList, ct); + + await SendNoContentAsync(ct); + } +} + +public record UpdateAttendanceListRequest( + bool Open, + DateTime ValidationStart, + DateTime ValidationEnd +); \ No newline at end of file diff --git a/src/ALB.Api/Endpoints/Children/Absence/CreateAbsenceForChildEndpoint.cs b/src/ALB.Api/Endpoints/Children/Absence/CreateAbsenceForChildEndpoint.cs index c2beef0..8960208 100644 --- a/src/ALB.Api/Endpoints/Children/Absence/CreateAbsenceForChildEndpoint.cs +++ b/src/ALB.Api/Endpoints/Children/Absence/CreateAbsenceForChildEndpoint.cs @@ -1,6 +1,7 @@ using System.Runtime.InteropServices.JavaScript; using ALB.Domain.Entities; using ALB.Domain.Repositories; +using ALB.Domain.Values; using FastEndpoints; using NodaTime; @@ -11,7 +12,7 @@ public class CreateAbsenceForChildEndpoint(IAbsenceDayRepository absenceRepo) : public override void Configure() { Post("/api/children/{childId:guid}/absence"); - Policies("ParentPolicy"); + Policies(SystemRoles.AdminPolicy); } public override async Task HandleAsync(CreateAbsenceRequest request, CancellationToken ct) @@ -31,7 +32,8 @@ public override async Task HandleAsync(CreateAbsenceRequest request, Cancellatio if (alreadyExists) { - await SendAsync(new CreateAbsenceResponse("An absence already exists for one or more days in this date range."), 409, ct); // 409 Conflict is better here + await SendAsync(new CreateAbsenceResponse("An absence already exists for one or more days in this date range."), 409, ct); + return; } @@ -47,7 +49,6 @@ public override async Task HandleAsync(CreateAbsenceRequest request, Cancellatio }; absencesToCreate.Add(absence); } - if (absencesToCreate.Any()) { @@ -61,4 +62,3 @@ public record CreateAbsenceRequest(DateTime StartDate, DateTime EndDate, int Abs public record CreateAbsenceResponse(string Message); - diff --git a/src/ALB.Api/Endpoints/Children/CreateChildEndpoint.cs b/src/ALB.Api/Endpoints/Children/CreateChildEndpoint.cs index 5e8ff3f..e595564 100644 --- a/src/ALB.Api/Endpoints/Children/CreateChildEndpoint.cs +++ b/src/ALB.Api/Endpoints/Children/CreateChildEndpoint.cs @@ -24,7 +24,7 @@ public override async Task HandleAsync(CreateChildRequest request, CancellationT DateOfBirth = request.DateOfBirth }; - var createdChild = await childRepository.CreateAsync(child); + var createdChild = await childRepository.CreateAsync(child, ct); await SendOkAsync( new CreateChildResponse(createdChild.Id, createdChild.FirstName, createdChild.LastName, diff --git a/src/ALB.Api/Endpoints/Children/DeleteChildEndpoint.cs b/src/ALB.Api/Endpoints/Children/DeleteChildEndpoint.cs index 5b65d89..05aa99b 100644 --- a/src/ALB.Api/Endpoints/Children/DeleteChildEndpoint.cs +++ b/src/ALB.Api/Endpoints/Children/DeleteChildEndpoint.cs @@ -16,7 +16,7 @@ public override async Task HandleAsync(CancellationToken ct) { var childId = Route("childId"); - var child = await childRepository.GetByIdAsync(childId); + var child = await childRepository.GetByIdAsync(childId, ct); if (child is null) { @@ -24,7 +24,7 @@ public override async Task HandleAsync(CancellationToken ct) return; } - await childRepository.DeleteAsync(childId); + await childRepository.DeleteAsync(childId, ct); await SendNoContentAsync(ct); } diff --git a/src/ALB.Api/Endpoints/Children/GetChildEndpoint.cs b/src/ALB.Api/Endpoints/Children/GetChildEndpoint.cs index d73399a..5f0e0f1 100644 --- a/src/ALB.Api/Endpoints/Children/GetChildEndpoint.cs +++ b/src/ALB.Api/Endpoints/Children/GetChildEndpoint.cs @@ -17,7 +17,7 @@ public override async Task HandleAsync(CancellationToken ct) { var childId = Route("childId"); - var child = await childRepository.GetByIdAsync(childId); + var child = await childRepository.GetByIdAsync(childId, ct); if (child is null) { diff --git a/src/ALB.Api/Endpoints/Children/GetChildrenEndpoint.cs b/src/ALB.Api/Endpoints/Children/GetChildrenEndpoint.cs new file mode 100644 index 0000000..a07cb42 --- /dev/null +++ b/src/ALB.Api/Endpoints/Children/GetChildrenEndpoint.cs @@ -0,0 +1,28 @@ +using ALB.Api.Extensions; +using ALB.Api.Models; +using ALB.Domain.Repositories; +using ALB.Domain.Values; +using FastEndpoints; +using NodaTime; + +namespace ALB.Api.Endpoints.Children; + +public class GetChildrenEndpoint(IChildRepository childRepository) : EndpointWithoutRequest +{ + public override void Configure() + { + Get("/api/children/"); + Policies(SystemRoles.AdminPolicy); + } + + public override async Task HandleAsync(CancellationToken ct) + { + var children = await childRepository.GetAllAsync(ct); + + var response = children.Select(c => c.ToDto()).ToList(); + + await SendAsync(new GetChildrenResponse(response), cancellation: ct); + } +} + +public record GetChildrenResponse(IEnumerable Children); \ No newline at end of file diff --git a/src/ALB.Api/Endpoints/Children/UpdateChildEndpoint.cs b/src/ALB.Api/Endpoints/Children/UpdateChildEndpoint.cs index afe0a3a..b96965c 100644 --- a/src/ALB.Api/Endpoints/Children/UpdateChildEndpoint.cs +++ b/src/ALB.Api/Endpoints/Children/UpdateChildEndpoint.cs @@ -13,14 +13,14 @@ public override void Configure() Policies(SystemRoles.AdminPolicy); } - public override async Task HandleAsync(UpdateChildRequest request, CancellationToken cancellationToken) + public override async Task HandleAsync(UpdateChildRequest request, CancellationToken ct) { var childId = Route("childId"); - var existingChild = await childRepository.GetByIdAsync(childId); + var existingChild = await childRepository.GetByIdAsync(childId, ct); if (existingChild is null) { - await SendNotFoundAsync(cancellationToken); + await SendNotFoundAsync(ct); return; } @@ -28,9 +28,9 @@ public override async Task HandleAsync(UpdateChildRequest request, CancellationT existingChild.LastName = request.ChildLastName; existingChild.DateOfBirth = request.ChildDateOfBirth; - await childRepository.UpdateAsync(existingChild); + await childRepository.UpdateAsync(existingChild, ct); - await SendNoContentAsync(cancellationToken); + await SendNoContentAsync(ct); } } diff --git a/src/ALB.Api/Endpoints/Groups/Children/AddChildrenToGroupEndpoint.cs b/src/ALB.Api/Endpoints/Groups/Children/AddChildrenToGroupEndpoint.cs index c3a8cec..dfa800d 100644 --- a/src/ALB.Api/Endpoints/Groups/Children/AddChildrenToGroupEndpoint.cs +++ b/src/ALB.Api/Endpoints/Groups/Children/AddChildrenToGroupEndpoint.cs @@ -1,4 +1,5 @@ using ALB.Domain.Repositories; +using ALB.Domain.Values; using FastEndpoints; namespace ALB.Api.Endpoints.Groups.Children; @@ -9,7 +10,7 @@ public class AddChildrenToGroupEndpoint(IGroupRepository repository) public override void Configure() { Post("/api/groups/{groupId:guid}/children"); - AllowAnonymous(); + Policies(SystemRoles.AdminPolicy); } public override async Task HandleAsync(AddChildToGroupRequest request, CancellationToken ct) diff --git a/src/ALB.Api/Endpoints/Groups/Children/RemoveChildrenFromGroupEndpoint.cs b/src/ALB.Api/Endpoints/Groups/Children/RemoveChildrenFromGroupEndpoint.cs index d08f25d..f31be61 100644 --- a/src/ALB.Api/Endpoints/Groups/Children/RemoveChildrenFromGroupEndpoint.cs +++ b/src/ALB.Api/Endpoints/Groups/Children/RemoveChildrenFromGroupEndpoint.cs @@ -1,4 +1,5 @@ using ALB.Domain.Repositories; +using ALB.Domain.Values; using FastEndpoints; namespace ALB.Api.Endpoints.Groups.Children; @@ -9,7 +10,7 @@ public class RemoveChildrenFromGroupEndpoint(IGroupRepository repository) public override void Configure() { Delete("/api/groups/{groupId:guid}/children"); - AllowAnonymous(); + Policies(SystemRoles.AdminPolicy); } public override async Task HandleAsync(RemoveChildFromGroupRequest request, CancellationToken ct) diff --git a/src/ALB.Api/Endpoints/Groups/Cohorts/CreateCohortEndpoint.cs b/src/ALB.Api/Endpoints/Groups/Cohorts/CreateCohortEndpoint.cs index 2523aed..c5e0d28 100644 --- a/src/ALB.Api/Endpoints/Groups/Cohorts/CreateCohortEndpoint.cs +++ b/src/ALB.Api/Endpoints/Groups/Cohorts/CreateCohortEndpoint.cs @@ -17,7 +17,7 @@ public override async Task HandleAsync(CreateCohortRequest request, Cancellation { var groupId = Route("groupId"); - var exists = await cohortRepo.ExistsAsync(request.CreationYear, groupId, request.GradeId); + var exists = await cohortRepo.ExistsAsync(request.CreationYear, groupId, request.GradeId, ct); if (exists) { AddError("Cohort with the same year, group and grade already exists."); @@ -33,7 +33,7 @@ public override async Task HandleAsync(CreateCohortRequest request, Cancellation GradeId = request.GradeId }; - var created = await cohortRepo.CreateAsync(cohort); + var created = await cohortRepo.CreateAsync(cohort, ct); await SendAsync(new CreateCohortResponse(created.Id, "Cohort created successfully."), cancellation: ct); } } diff --git a/src/ALB.Api/Endpoints/Groups/Cohorts/GetCohortsOfGroupEndpoint.cs b/src/ALB.Api/Endpoints/Groups/Cohorts/GetCohortsOfGroupEndpoint.cs new file mode 100644 index 0000000..9f56a1d --- /dev/null +++ b/src/ALB.Api/Endpoints/Groups/Cohorts/GetCohortsOfGroupEndpoint.cs @@ -0,0 +1,29 @@ +using ALB.Api.Extensions; +using ALB.Api.Models; +using ALB.Domain.Entities; +using ALB.Domain.Repositories; +using FastEndpoints; + +namespace ALB.Api.Endpoints.Groups.Cohorts; + +public class GetCohortsOfGroupEndpoint(IGroupRepository groupRepository) : EndpointWithoutRequest +{ + public override void Configure() + { + Get("/api/groups/{groupId:guid}/cohorts"); + Policies("AdminPolicy"); + } + + public override async Task HandleAsync(CancellationToken ct) + { + var groupId = Route("groupId"); + + var cohorts = (await groupRepository.GetAllCohortsOfGroupAsync(groupId, ct)) + .Select(g => g.ToDto()) + .ToList(); + + await SendAsync(new GetCohortsOfGroupResponse(cohorts), cancellation: ct); + } +} + +public record GetCohortsOfGroupResponse(IEnumerable Cohorts); \ No newline at end of file diff --git a/src/ALB.Api/Endpoints/Groups/CreateGroupEndpoint.cs b/src/ALB.Api/Endpoints/Groups/CreateGroupEndpoint.cs index 280fb77..dd18857 100644 --- a/src/ALB.Api/Endpoints/Groups/CreateGroupEndpoint.cs +++ b/src/ALB.Api/Endpoints/Groups/CreateGroupEndpoint.cs @@ -13,24 +13,24 @@ public override void Configure() Policies(SystemRoles.AdminPolicy); } - public override async Task HandleAsync(CreateGroupRequest request, CancellationToken cancellationToken) + public override async Task HandleAsync(CreateGroupRequest request, CancellationToken ct) { var group = new Group { Id = Guid.NewGuid(), - Name = request.GroupName, + Name = request.Name, ResponsibleUserId = request.ResponsibleUserId }; - var createdGroup = await groupRepository.CreateAsync(group); + var createdGroup = await groupRepository.CreateAsync(group, ct); await SendAsync( new CreateGroupResponse(createdGroup.Id, "Group created successfully."), - cancellation: cancellationToken + cancellation: ct ); } } -public record CreateGroupRequest(string GroupName, Guid ResponsibleUserId); +public record CreateGroupRequest(string Name, Guid ResponsibleUserId); public record CreateGroupResponse(Guid Id, string Message); \ No newline at end of file diff --git a/src/ALB.Api/Endpoints/Groups/DeleteGroupEndpoint.cs b/src/ALB.Api/Endpoints/Groups/DeleteGroupEndpoint.cs index 16348d9..f0f7a61 100644 --- a/src/ALB.Api/Endpoints/Groups/DeleteGroupEndpoint.cs +++ b/src/ALB.Api/Endpoints/Groups/DeleteGroupEndpoint.cs @@ -12,12 +12,12 @@ public override void Configure() Policies(SystemRoles.AdminPolicy); } - public override async Task HandleAsync(DeleteGroupRequest request, CancellationToken cancellationToken) + public override async Task HandleAsync(DeleteGroupRequest request, CancellationToken ct) { - await groupRepository.DeleteAsync(request.GroupId); + await groupRepository.DeleteAsync(request.GroupId, ct); await SendAsync(new DeleteGroupResponse($"Deleted Group with Id: {request.GroupId}"), - cancellation: cancellationToken); + cancellation: ct); } } diff --git a/src/ALB.Api/Endpoints/Groups/GetGroupEndpoint.cs b/src/ALB.Api/Endpoints/Groups/GetGroupEndpoint.cs new file mode 100644 index 0000000..62922c1 --- /dev/null +++ b/src/ALB.Api/Endpoints/Groups/GetGroupEndpoint.cs @@ -0,0 +1,35 @@ +using ALB.Api.Extensions; +using ALB.Api.Models; +using ALB.Domain.Repositories; +using ALB.Domain.Values; +using FastEndpoints; + +namespace ALB.Api.Endpoints.Groups; + +public class GetGroupEndpoint(IGroupRepository groupRepository) : EndpointWithoutRequest +{ + public override void Configure() + { + Get("/api/groups/{groupId:guid}"); + Policies(SystemRoles.AdminPolicy); + } + + public override async Task HandleAsync(CancellationToken ct) + { + var groupId = Route("groupId"); + + var group = await groupRepository.GetByIdAsync(groupId, ct); + + if (group is null) + { + await SendNotFoundAsync(ct); + return; + } + + var response = new GetGroupResponse(group.ToDto()); + + await SendAsync(response, cancellation: ct); + } +} + +public record GetGroupResponse(GroupDto Group); \ No newline at end of file diff --git a/src/ALB.Api/Endpoints/Groups/GetGroupsEndpoint.cs b/src/ALB.Api/Endpoints/Groups/GetGroupsEndpoint.cs new file mode 100644 index 0000000..cf37692 --- /dev/null +++ b/src/ALB.Api/Endpoints/Groups/GetGroupsEndpoint.cs @@ -0,0 +1,27 @@ +using ALB.Api.Extensions; +using ALB.Api.Models; +using ALB.Domain.Repositories; +using ALB.Domain.Values; +using FastEndpoints; + +namespace ALB.Api.Endpoints.Groups; + +public class GetGroupsEndpoint(IGroupRepository groupRepository) : EndpointWithoutRequest +{ + public override void Configure() + { + Get("/api/groups/"); + Policies(SystemRoles.AdminPolicy); + } + + public override async Task HandleAsync(CancellationToken ct) + { + var groups = (await groupRepository.GetAllAsync(ct)) + .Select(g => g.ToDto()) + .ToList(); + + await SendAsync(new GetGroupsResponse(groups), cancellation: ct); + } +} + +public record GetGroupsResponse(IEnumerable Groups); \ No newline at end of file diff --git a/src/ALB.Api/Endpoints/Groups/UpdateGroupEndpoint.cs b/src/ALB.Api/Endpoints/Groups/UpdateGroupEndpoint.cs index 14cc68e..cf41684 100644 --- a/src/ALB.Api/Endpoints/Groups/UpdateGroupEndpoint.cs +++ b/src/ALB.Api/Endpoints/Groups/UpdateGroupEndpoint.cs @@ -12,29 +12,29 @@ public override void Configure() Policies(SystemRoles.AdminPolicy); } - public override async Task HandleAsync(UpdateGroupRequest request, CancellationToken cancellationToken) + public override async Task HandleAsync(UpdateGroupRequest request, CancellationToken ct) { var groupId = Route("groupId"); - var existingGroup = await groupRepository.GetByIdAsync(groupId); + var existingGroup = await groupRepository.GetByIdAsync(groupId, ct); if (existingGroup is null) { - await SendNotFoundAsync(cancellationToken); + await SendNotFoundAsync(ct); return; } existingGroup.Name = request.GroupName; - await groupRepository.UpdateAsync(existingGroup); + await groupRepository.UpdateAsync(existingGroup, ct); await SendAsync( new UpdateGroupResponse($"Updated group '{existingGroup.Name}' with ID: {existingGroup.Id}"), - cancellation: cancellationToken + cancellation: ct ); } } -public record UpdateGroupRequest(Guid groupId, string GroupName); +public record UpdateGroupRequest(string GroupName); public record UpdateGroupResponse(string Message); \ No newline at end of file diff --git a/src/ALB.Api/Endpoints/Users/GetUsersEndpoint.cs b/src/ALB.Api/Endpoints/Users/GetUsersEndpoint.cs index bea836a..c878001 100644 --- a/src/ALB.Api/Endpoints/Users/GetUsersEndpoint.cs +++ b/src/ALB.Api/Endpoints/Users/GetUsersEndpoint.cs @@ -18,11 +18,9 @@ public override void Configure() public override async Task HandleAsync(CancellationToken ct) { - var userDtos = await userManager.Users.Select(u => u.ToDto()).ToListAsync(ct); + var users = await userManager.Users.Select(u => u.ToDto()).ToListAsync(ct); - var response = new GetUsersResponse(userDtos); - - await SendAsync(response, cancellation: ct); + await SendAsync(new GetUsersResponse(users), cancellation: ct); } } diff --git a/src/ALB.Api/Extensions/AttendanceListEntryExtension.cs b/src/ALB.Api/Extensions/AttendanceListEntryExtension.cs new file mode 100644 index 0000000..3063511 --- /dev/null +++ b/src/ALB.Api/Extensions/AttendanceListEntryExtension.cs @@ -0,0 +1,20 @@ +using ALB.Api.Models; +using ALB.Domain.Entities; + +namespace ALB.Api.Extensions; + +public static class AttendanceListEntryExtension +{ + public static AttendanceListEntryDto ToDto(this AttendanceListEntry al) + { + return new AttendanceListEntryDto( + al.Id, + al.Date.ToDateOnly(), + al.ArrivalAt.ToTimeOnly(), + al.DepartureAt.ToTimeOnly(), + al.AttendanceListId, + al.AttendanceStatusId, + al.ChildId + ); + } +} \ No newline at end of file diff --git a/src/ALB.Api/Extensions/AttendanceListExtension.cs b/src/ALB.Api/Extensions/AttendanceListExtension.cs new file mode 100644 index 0000000..1494e84 --- /dev/null +++ b/src/ALB.Api/Extensions/AttendanceListExtension.cs @@ -0,0 +1,18 @@ +using ALB.Api.Models; +using ALB.Domain.Entities; + +namespace ALB.Api.Extensions; + +public static class AttendanceListExtension +{ + public static AttendanceListDto ToDto(this AttendanceList al) + { + return new AttendanceListDto( + al.Id, + al.CohortId, + al.Open, + al.ValidationPeriod.Start.ToDateOnly(), + al.ValidationPeriod.End.ToDateOnly() + ); + } +} \ No newline at end of file diff --git a/src/ALB.Api/Extensions/ChildExtensions.cs b/src/ALB.Api/Extensions/ChildExtensions.cs new file mode 100644 index 0000000..c0bccf6 --- /dev/null +++ b/src/ALB.Api/Extensions/ChildExtensions.cs @@ -0,0 +1,17 @@ +using ALB.Api.Models; +using ALB.Domain.Entities; + +namespace ALB.Api.Extensions; + +public static class ChildExtensions +{ + public static ChildDto ToDto(this Child child) + { + return new ChildDto( + child.Id, + child.FirstName, + child.LastName, + child.DateOfBirth.ToDateOnly(), + child.GroupId); + } +} \ No newline at end of file diff --git a/src/ALB.Api/Extensions/CohortExtension.cs b/src/ALB.Api/Extensions/CohortExtension.cs new file mode 100644 index 0000000..8318901 --- /dev/null +++ b/src/ALB.Api/Extensions/CohortExtension.cs @@ -0,0 +1,17 @@ +using ALB.Api.Models; +using ALB.Domain.Entities; + +namespace ALB.Api.Extensions; + +public static class CohortExtension +{ + public static CohortDto ToDto(this Cohort cohort) + { + return new CohortDto( + cohort.Id, + cohort.CreationYear, + cohort.GroupId, + cohort.GradeId + ); + } +} \ No newline at end of file diff --git a/src/ALB.Api/Extensions/DateTimeExtensions.cs b/src/ALB.Api/Extensions/DateTimeExtensions.cs index ce3b7b7..7033b43 100644 --- a/src/ALB.Api/Extensions/DateTimeExtensions.cs +++ b/src/ALB.Api/Extensions/DateTimeExtensions.cs @@ -11,6 +11,21 @@ public static LocalTime ToNodaLocalTime(this DateTime dateTime) public static LocalTime? ToNodaLocalTime(this DateTime? dateTime) => dateTime.HasValue ? LocalTime.FromTimeOnly(TimeOnly.FromDateTime(dateTime.Value)) : null; + + public static DateOnly ToDateOnly(this LocalDate d) => new(d.Year, d.Month, d.Day); + public static LocalDate ToLocalDate(this DateOnly d) => new(d.Year, d.Month, d.Day); + + public static LocalTime ToLocalTime(this TimeOnly t) + => LocalTime.FromTicksSinceMidnight(t.Ticks); + + public static LocalTime? ToLocalTime(this TimeOnly? t) + => t.HasValue ? LocalTime.FromTicksSinceMidnight(t.Value.Ticks) : (LocalTime?)null; + + public static TimeOnly ToTimeOnly(this LocalTime t) + => TimeOnly.FromTimeSpan(TimeSpan.FromTicks(t.TickOfDay)); + + public static TimeOnly? ToTimeOnly(this LocalTime? t) + => t.HasValue ? TimeOnly.FromTimeSpan(TimeSpan.FromTicks(t.Value.TickOfDay)) : (TimeOnly?)null; public static IServiceCollection AddNodaTimeJsonConverters(this IServiceCollection serviceCollection) { diff --git a/src/ALB.Api/Extensions/GroupExtensions.cs b/src/ALB.Api/Extensions/GroupExtensions.cs new file mode 100644 index 0000000..414b29d --- /dev/null +++ b/src/ALB.Api/Extensions/GroupExtensions.cs @@ -0,0 +1,16 @@ +using ALB.Api.Models; +using ALB.Domain.Entities; + +namespace ALB.Api.Extensions; + +public static class GroupExtensions +{ + public static GroupDto ToDto(this Group group) + { + return new GroupDto( + group.Id, + group.Name, + group.ResponsibleUserId + ); + } +} \ No newline at end of file diff --git a/src/ALB.Api/Models/AttendanceListDto.cs b/src/ALB.Api/Models/AttendanceListDto.cs new file mode 100644 index 0000000..61a95bc --- /dev/null +++ b/src/ALB.Api/Models/AttendanceListDto.cs @@ -0,0 +1,9 @@ +namespace ALB.Api.Models; + +public record AttendanceListDto( + Guid Id, + Guid CohortId, + bool Open, + DateOnly ValidationStart, + DateOnly ValidationEnd +); \ No newline at end of file diff --git a/src/ALB.Api/Models/AttendanceListEntryDto.cs b/src/ALB.Api/Models/AttendanceListEntryDto.cs new file mode 100644 index 0000000..338caf3 --- /dev/null +++ b/src/ALB.Api/Models/AttendanceListEntryDto.cs @@ -0,0 +1,11 @@ +namespace ALB.Api.Models; + +public record AttendanceListEntryDto( + Guid Id, + DateOnly Date, + TimeOnly? ArrivalAt, + TimeOnly? DepartureAt, + Guid AttendanceListId, + int AttendanceStatusId, + Guid ChildId + ); \ No newline at end of file diff --git a/src/ALB.Api/Models/ChildDto.cs b/src/ALB.Api/Models/ChildDto.cs new file mode 100644 index 0000000..f46a466 --- /dev/null +++ b/src/ALB.Api/Models/ChildDto.cs @@ -0,0 +1,11 @@ +using NodaTime; + +namespace ALB.Api.Models; + +public record ChildDto( + Guid Id, + string? FirstName, + string? LastName, + DateOnly DateOfBirth, + Guid? GroupId +); \ No newline at end of file diff --git a/src/ALB.Api/Models/CohortDto.cs b/src/ALB.Api/Models/CohortDto.cs new file mode 100644 index 0000000..0662ce2 --- /dev/null +++ b/src/ALB.Api/Models/CohortDto.cs @@ -0,0 +1,7 @@ +namespace ALB.Api.Models; + +public record CohortDto( + Guid Id, + int CreationYear, + Guid GroupId, + Guid GradeId); \ No newline at end of file diff --git a/src/ALB.Api/Models/GroupDto.cs b/src/ALB.Api/Models/GroupDto.cs new file mode 100644 index 0000000..75bdeaa --- /dev/null +++ b/src/ALB.Api/Models/GroupDto.cs @@ -0,0 +1,7 @@ +namespace ALB.Api.Models; + +public record GroupDto( + Guid Id, + string? Name, + Guid ResponsibleUserId +); \ No newline at end of file diff --git a/src/ALB.Api/Program.cs b/src/ALB.Api/Program.cs index 8ebfbff..58478e1 100644 --- a/src/ALB.Api/Program.cs +++ b/src/ALB.Api/Program.cs @@ -25,6 +25,18 @@ ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto; }); +builder.Services.AddCors(options => +{ + options.AddPolicy("DevPolicy", + builder => + { + builder.WithOrigins("http://localhost:8080", "http://localhost:8000") + .AllowAnyMethod() + .AllowAnyHeader() + .AllowCredentials(); // Wichtig für Auth-Cookies + }); +}); + builder.Services.AddInfrastructure(builder.Configuration); builder.Services.AddScoped(); @@ -32,6 +44,7 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddOpenApi(); @@ -47,6 +60,7 @@ var app = builder.Build(); // Configure the HTTP request pipeline. +app.UseCors("DevPolicy"); app.UseExceptionHandler("/Error"); app.UseForwardedHeaders(); app.UseAuthentication(); diff --git a/src/ALB.Domain/Entities/AbsenceDay.cs b/src/ALB.Domain/Entities/AbsenceDay.cs index e4be269..e13b09e 100644 --- a/src/ALB.Domain/Entities/AbsenceDay.cs +++ b/src/ALB.Domain/Entities/AbsenceDay.cs @@ -9,8 +9,8 @@ public class AbsenceDay public LocalDate Date { get; set; } public Guid ChildId { get; set; } public int AbsenceStatusId { get; set; } - + public AbsenceStatus AbsenceStatus { get; set; } = null!; public Child Child { get; set; } = null!; - public AbsenceStatus AbsenceStatus { get; set; } = null!; + } \ No newline at end of file diff --git a/src/ALB.Domain/Repositories/IAbsenceDayRepository.cs b/src/ALB.Domain/Repositories/IAbsenceDayRepository.cs index ab4a3d3..fe9a14b 100644 --- a/src/ALB.Domain/Repositories/IAbsenceDayRepository.cs +++ b/src/ALB.Domain/Repositories/IAbsenceDayRepository.cs @@ -5,9 +5,9 @@ namespace ALB.Domain.Repositories; public interface IAbsenceDayRepository { - Task AddAsync(AbsenceDay absenceDay); - Task ExistsAsync(Guid childId, LocalDate date); - Task> GetByDateAsync(LocalDate date); - Task AddRangeAsync(IEnumerable absenceDays, CancellationToken cancellationToken); - Task ExistsInRangeAsync(Guid childId, LocalDate startDate, LocalDate endDate, CancellationToken cancellationToken); + Task AddAsync(AbsenceDay absenceDay, CancellationToken ct); + Task ExistsAsync(Guid childId, LocalDate date, CancellationToken ct); + Task> GetByDateAsync(LocalDate date, CancellationToken ct); + Task AddRangeAsync(IEnumerable absenceDays, CancellationToken ct); + Task ExistsInRangeAsync(Guid childId, LocalDate startDate, LocalDate endDate, CancellationToken ct); } \ No newline at end of file diff --git a/src/ALB.Domain/Repositories/IAttendanceListRepository.cs b/src/ALB.Domain/Repositories/IAttendanceListRepository.cs new file mode 100644 index 0000000..ca1b491 --- /dev/null +++ b/src/ALB.Domain/Repositories/IAttendanceListRepository.cs @@ -0,0 +1,13 @@ +using ALB.Domain.Entities; + +namespace ALB.Domain.Repositories; + +public interface IAttendanceListRepository +{ + Task CreateAsync(AttendanceList attendanceList, CancellationToken ct); + Task GetByIdAsync(Guid id, CancellationToken ct); + Task> GetAllAsync(CancellationToken ct); + Task UpdateAsync(AttendanceList attendanceList, CancellationToken ct); + Task DeleteAsync(Guid id, CancellationToken ct); + Task> GetAttendancesOfAttendanceListAsync(Guid attendanceListId, CancellationToken ct); +} \ No newline at end of file diff --git a/src/ALB.Domain/Repositories/IAttendanceRepository.cs b/src/ALB.Domain/Repositories/IAttendanceRepository.cs index e2b66ac..c7c0243 100644 --- a/src/ALB.Domain/Repositories/IAttendanceRepository.cs +++ b/src/ALB.Domain/Repositories/IAttendanceRepository.cs @@ -6,8 +6,10 @@ namespace ALB.Domain.Repositories; public interface IAttendanceRepository { - Task CreateAsync(Guid childId, LocalDate date, LocalTime? arrivalAt, LocalTime? departureAt, AttendanceStatus status, CancellationToken ct); - Task UpdateAsync(Guid childId, LocalDate date, LocalTime? arrivalAt, LocalTime? departureAt, AttendanceStatus status, CancellationToken ct); - Task DeleteAsync(Guid childId, LocalDate date, CancellationToken ct); - Task GetAttendanceListByIdAsync(Guid id); + Task CreateAsync(AttendanceListEntry entry, CancellationToken ct); + Task ExistsAsync(Guid attendanceListId, Guid childId, LocalDate date, CancellationToken ct); + Task UpdateAsync(AttendanceListEntry attendanceListEntry, CancellationToken ct); + Task DeleteAsync(Guid id, CancellationToken ct); + Task GetByListChildAndDateAsync(Guid attendanceListId, Guid childId, LocalDate date, + CancellationToken ct); } \ No newline at end of file diff --git a/src/ALB.Domain/Repositories/IChildRepository.cs b/src/ALB.Domain/Repositories/IChildRepository.cs index 83050d5..c09529d 100644 --- a/src/ALB.Domain/Repositories/IChildRepository.cs +++ b/src/ALB.Domain/Repositories/IChildRepository.cs @@ -4,11 +4,11 @@ namespace ALB.Domain.Repositories; public interface IChildRepository { - TaskCreateAsync(Child child); - Task GetByIdAsync(Guid id); - Task> GetAllAsync(); - Task UpdateAsync(Child child); - Task DeleteAsync(Guid id); - Task> GetByCohortAsync(Guid cohortId); + TaskCreateAsync(Child child, CancellationToken ct); + Task GetByIdAsync(Guid id, CancellationToken ct); + Task> GetAllAsync(CancellationToken ct); + Task UpdateAsync(Child child, CancellationToken ct); + Task DeleteAsync(Guid id, CancellationToken ct); + Task> GetByCohortAsync(Guid cohortId, CancellationToken ct); } \ No newline at end of file diff --git a/src/ALB.Domain/Repositories/ICohortRepository.cs b/src/ALB.Domain/Repositories/ICohortRepository.cs index e488536..b774c20 100644 --- a/src/ALB.Domain/Repositories/ICohortRepository.cs +++ b/src/ALB.Domain/Repositories/ICohortRepository.cs @@ -4,7 +4,7 @@ namespace ALB.Domain.Repositories; public interface ICohortRepository { - Task CreateAsync(Cohort cohort); - Task ExistsAsync(int year, Guid groupId, Guid gradeId); + Task CreateAsync(Cohort cohort, CancellationToken ct); + Task ExistsAsync(int year, Guid groupId, Guid gradeId, CancellationToken ct); } diff --git a/src/ALB.Domain/Repositories/IGroupRepository.cs b/src/ALB.Domain/Repositories/IGroupRepository.cs index 5dbc57d..0a932e9 100644 --- a/src/ALB.Domain/Repositories/IGroupRepository.cs +++ b/src/ALB.Domain/Repositories/IGroupRepository.cs @@ -4,12 +4,12 @@ namespace ALB.Domain.Repositories; public interface IGroupRepository { - Task CreateAsync(Group group); - Task GetByIdAsync(Guid id); - Task UpdateAsync(Group group); - Task DeleteAsync(Guid id); - + Task CreateAsync(Group group, CancellationToken ct); + Task GetByIdAsync(Guid id, CancellationToken ct); + Task UpdateAsync(Group group, CancellationToken ct); + Task DeleteAsync(Guid id, CancellationToken ct); + Task> GetAllAsync(CancellationToken ct); Task AddChildrenToGroupAsync(Guid groupId, IEnumerable childIds, CancellationToken ct); Task RemoveChildrenFromGroupAsync(Guid groupId, IEnumerable childIds, CancellationToken ct); - + Task> GetAllCohortsOfGroupAsync(Guid groupId, CancellationToken ct); } \ No newline at end of file diff --git a/src/ALB.Infrastructure/Persistence/Repositories/AbsenceDayRepository.cs b/src/ALB.Infrastructure/Persistence/Repositories/AbsenceDayRepository.cs index 611b2bd..e9df605 100644 --- a/src/ALB.Infrastructure/Persistence/Repositories/AbsenceDayRepository.cs +++ b/src/ALB.Infrastructure/Persistence/Repositories/AbsenceDayRepository.cs @@ -1,48 +1,40 @@ using ALB.Domain.Entities; using ALB.Domain.Repositories; -using ALB.Infrastructure.Persistence; using Microsoft.EntityFrameworkCore; using NodaTime; namespace ALB.Infrastructure.Persistence.Repositories; -public class AbsenceDayRepository : IAbsenceDayRepository +public class AbsenceDayRepository(ApplicationDbContext dbContext) : IAbsenceDayRepository { - private readonly ApplicationDbContext _dbContext; - - public AbsenceDayRepository(ApplicationDbContext dbContext) - { - _dbContext = dbContext; - } - - public async Task AddAsync(AbsenceDay absenceDay) + public async Task AddAsync(AbsenceDay absenceDay, CancellationToken ct) { - _dbContext.AbsenceDays.Add(absenceDay); - await _dbContext.SaveChangesAsync(); + dbContext.AbsenceDays.Add(absenceDay); + await dbContext.SaveChangesAsync(ct); } - public async Task ExistsAsync(Guid childId, LocalDate date) + public async Task ExistsAsync(Guid childId, LocalDate date, CancellationToken ct) { - return await _dbContext.AbsenceDays - .AnyAsync(a => a.ChildId == childId && a.Date == date); + return await dbContext.AbsenceDays + .AnyAsync(a => a.ChildId == childId && a.Date == date, ct); } - public async Task> GetByDateAsync(LocalDate date) + public async Task> GetByDateAsync(LocalDate date, CancellationToken ct) { - return await _dbContext.AbsenceDays + return await dbContext.AbsenceDays .Where(a => a.Date == date) - .ToListAsync(); + .ToListAsync(ct); } public async Task AddRangeAsync(IEnumerable absenceDays, CancellationToken cancellationToken) { - await _dbContext.AbsenceDays.AddRangeAsync(absenceDays, cancellationToken); - await _dbContext.SaveChangesAsync(cancellationToken); + await dbContext.AbsenceDays.AddRangeAsync(absenceDays, cancellationToken); + await dbContext.SaveChangesAsync(cancellationToken); } public async Task ExistsInRangeAsync(Guid childId, LocalDate startDate, LocalDate endDate, CancellationToken cancellationToken) { - return await _dbContext.AbsenceDays + return await dbContext.AbsenceDays .AnyAsync(ad => ad.ChildId == childId && ad.Date >= startDate && ad.Date <= endDate, cancellationToken); } } \ No newline at end of file diff --git a/src/ALB.Infrastructure/Persistence/Repositories/AttendanceListRepository.cs b/src/ALB.Infrastructure/Persistence/Repositories/AttendanceListRepository.cs new file mode 100644 index 0000000..90bd64b --- /dev/null +++ b/src/ALB.Infrastructure/Persistence/Repositories/AttendanceListRepository.cs @@ -0,0 +1,53 @@ +using ALB.Domain.Entities; +using ALB.Domain.Repositories; +using Microsoft.EntityFrameworkCore; + +namespace ALB.Infrastructure.Persistence.Repositories; + +public class AttendanceListRepository(ApplicationDbContext dbContext) : IAttendanceListRepository +{ + public async Task CreateAsync(AttendanceList attendanceList, CancellationToken ct) + { + dbContext.AttendanceLists.Add(attendanceList); + await dbContext.SaveChangesAsync(ct); + return attendanceList; + } + + public async Task GetByIdAsync(Guid id, CancellationToken ct) + { + return await dbContext.AttendanceLists + .Include(al => al.Cohort) + .FirstOrDefaultAsync(al => al.Id == id, ct); + } + + public async Task> GetAllAsync(CancellationToken ct) + { + return await dbContext.AttendanceLists + .Include(al => al.Cohort) + .ToListAsync(ct); + } + + public async Task UpdateAsync(AttendanceList attendanceList, CancellationToken ct) + { + dbContext.AttendanceLists.Update(attendanceList); + await dbContext.SaveChangesAsync(ct); + } + + public async Task DeleteAsync(Guid id, CancellationToken ct) + { + var attendanceList = await dbContext.AttendanceLists.FindAsync([id], ct); + if (attendanceList is not null) + { + dbContext.AttendanceLists.Remove(attendanceList); + await dbContext.SaveChangesAsync(ct); + } + } + + public async Task> GetAttendancesOfAttendanceListAsync(Guid attendanceListId, + CancellationToken ct) + { + return await dbContext.AttendanceListEntries + .Where(c => c.AttendanceListId == attendanceListId) + .ToListAsync(ct); + } +} \ No newline at end of file diff --git a/src/ALB.Infrastructure/Persistence/Repositories/AttendanceRepository.cs b/src/ALB.Infrastructure/Persistence/Repositories/AttendanceRepository.cs index 22ab459..61a609e 100644 --- a/src/ALB.Infrastructure/Persistence/Repositories/AttendanceRepository.cs +++ b/src/ALB.Infrastructure/Persistence/Repositories/AttendanceRepository.cs @@ -7,64 +7,45 @@ namespace ALB.Infrastructure.Persistence.Repositories; public class AttendanceRepository(ApplicationDbContext dbContext) : IAttendanceRepository -{ - public async Task CreateAsync(Guid childId, LocalDate date, LocalTime? arrivalAt, LocalTime? departureAt, - AttendanceStatus status, CancellationToken ct) +{ + public async Task CreateAsync(AttendanceListEntry entry, CancellationToken ct) { - var attendance = await dbContext.AttendanceListEntries - .FirstOrDefaultAsync(a => a.ChildId == childId && a.Date == date, ct); - - if (attendance is null) - { - attendance = new AttendanceListEntry - { - Id = Guid.NewGuid(), - ChildId = childId, - Date = date, - ArrivalAt = arrivalAt, - DepartureAt = departureAt, - AttendanceStatus = status - }; - - dbContext.AttendanceListEntries.Add(attendance); - - await dbContext.SaveChangesAsync(ct); - } + dbContext.AttendanceListEntries.Add(entry); + await dbContext.SaveChangesAsync(ct); + return entry; } - - public async Task UpdateAsync(Guid childId, LocalDate date, LocalTime? arrivalAt, LocalTime? departureAt, - AttendanceStatus status, CancellationToken ct) + + public async Task ExistsAsync(Guid attendanceListId, Guid childId, LocalDate date, CancellationToken ct) + { + return await dbContext.AttendanceListEntries + .AnyAsync(e => e.AttendanceListId == attendanceListId && + e.ChildId == childId && + e.Date == date, ct); + } + + public async Task UpdateAsync(AttendanceListEntry attendanceListEntry, CancellationToken ct) { - var attendance = await dbContext.AttendanceListEntries - .FirstOrDefaultAsync(a => a.ChildId == childId && a.Date == date, ct); + dbContext.AttendanceListEntries.Update(attendanceListEntry); + await dbContext.SaveChangesAsync(ct); + } + + public async Task DeleteAsync(Guid id, CancellationToken ct) + { + var entry = await dbContext.AttendanceListEntries.FindAsync([id], ct); - if (attendance is not null) + if (entry is not null) { - attendance.ArrivalAt = arrivalAt; - attendance.DepartureAt = departureAt; - attendance.AttendanceStatus = status; - dbContext.AttendanceListEntries.Update(attendance); - + dbContext.AttendanceListEntries.Remove(entry); await dbContext.SaveChangesAsync(ct); } } - - public async Task DeleteAsync(Guid childId, LocalDate date, CancellationToken ct) - { - var attendance = await dbContext.AttendanceListEntries - .FirstOrDefaultAsync(a => a.ChildId == childId && a.Date == date, ct); - - if (attendance is null) - throw new Exception("Attendance not found"); - - dbContext.AttendanceListEntries.Remove(attendance); - await dbContext.SaveChangesAsync(ct); - } - public async Task GetAttendanceListByIdAsync(Guid id) + public async Task GetByListChildAndDateAsync(Guid attendanceListId, Guid childId, LocalDate date, CancellationToken ct) { - return await dbContext.AttendanceLists - .Include(al => al.Cohort) - .FirstOrDefaultAsync(al => al.Id == id); + return await dbContext.AttendanceListEntries + .Include(e => e.Child) + .Include(e => e.AttendanceStatus) + .Where(e => e.AttendanceListId == attendanceListId && e.Date == date) + .FirstOrDefaultAsync(ct); } } \ No newline at end of file diff --git a/src/ALB.Infrastructure/Persistence/Repositories/ChildRepository.cs b/src/ALB.Infrastructure/Persistence/Repositories/ChildRepository.cs index 2266cda..e117fbe 100644 --- a/src/ALB.Infrastructure/Persistence/Repositories/ChildRepository.cs +++ b/src/ALB.Infrastructure/Persistence/Repositories/ChildRepository.cs @@ -6,43 +6,44 @@ namespace ALB.Infrastructure.Persistence.Repositories; public class ChildRepository(ApplicationDbContext dbContext) : IChildRepository { - public async Task CreateAsync(Child child) + public async Task CreateAsync(Child child, CancellationToken ct) { dbContext.Children.Add(child); - await dbContext.SaveChangesAsync(); + await dbContext.SaveChangesAsync(ct); return child; } - public async Task GetByIdAsync(Guid id) + public async Task GetByIdAsync(Guid id, CancellationToken ct) { - return await dbContext.Children.FindAsync(id); + return await dbContext.Children.FindAsync([id], ct); } - public async Task> GetAllAsync() + public async Task> GetAllAsync(CancellationToken ct) { - return await dbContext.Children.ToListAsync(); + return await dbContext.Children.ToListAsync(ct); } - public async Task UpdateAsync(Child child) + public async Task UpdateAsync(Child child, CancellationToken ct) { dbContext.Children.Update(child); - await dbContext.SaveChangesAsync(); + await dbContext.SaveChangesAsync(ct); } - public async Task DeleteAsync(Guid id) + public async Task DeleteAsync(Guid id, CancellationToken ct) { - var child = await dbContext.Children.FindAsync(id); + var child = await dbContext.Children.FindAsync([id], ct); if (child is not null) { dbContext.Children.Remove(child); - await dbContext.SaveChangesAsync(); + await dbContext.SaveChangesAsync(ct); } } - public async Task> GetByCohortAsync(Guid cohortId) + public async Task> GetByCohortAsync(Guid cohortId, CancellationToken ct) { return await dbContext.Children + .Include(c => c.Group).ThenInclude(g => g.Cohorts) .Where(child => child.Group.Cohorts.Any(c => c.Id == cohortId)) - .ToListAsync(); + .ToListAsync(ct); } } \ No newline at end of file diff --git a/src/ALB.Infrastructure/Persistence/Repositories/CohortRepository.cs b/src/ALB.Infrastructure/Persistence/Repositories/CohortRepository.cs index c793b92..e30a5a4 100644 --- a/src/ALB.Infrastructure/Persistence/Repositories/CohortRepository.cs +++ b/src/ALB.Infrastructure/Persistence/Repositories/CohortRepository.cs @@ -4,28 +4,21 @@ namespace ALB.Infrastructure.Persistence.Repositories; -public class CohortRepository : ICohortRepository +public class CohortRepository(ApplicationDbContext db) : ICohortRepository { - private readonly ApplicationDbContext _db; - - public CohortRepository(ApplicationDbContext db) - { - _db = db; - } - - public async Task CreateAsync(Cohort cohort) + public async Task CreateAsync(Cohort cohort, CancellationToken ct) { - _db.Cohorts.Add(cohort); - await _db.SaveChangesAsync(); + db.Cohorts.Add(cohort); + await db.SaveChangesAsync(ct); return cohort; } - public async Task ExistsAsync(int year, Guid groupId, Guid gradeId) + public async Task ExistsAsync(int year, Guid groupId, Guid gradeId, CancellationToken ct) { - return await _db.Cohorts.AnyAsync(c => + return await db.Cohorts.AnyAsync(c => c.CreationYear == year && c.GroupId == groupId && - c.GradeId == gradeId + c.GradeId == gradeId, ct ); } } \ No newline at end of file diff --git a/src/ALB.Infrastructure/Persistence/Repositories/GroupRepository.cs b/src/ALB.Infrastructure/Persistence/Repositories/GroupRepository.cs index bbf6fe9..eeba855 100644 --- a/src/ALB.Infrastructure/Persistence/Repositories/GroupRepository.cs +++ b/src/ALB.Infrastructure/Persistence/Repositories/GroupRepository.cs @@ -6,34 +6,39 @@ namespace ALB.Infrastructure.Persistence.Repositories; public class GroupRepository(ApplicationDbContext dbContext) : IGroupRepository { - public async Task CreateAsync(Group group) + public async Task CreateAsync(Group group, CancellationToken ct) { dbContext.Groups.Add(group); - await dbContext.SaveChangesAsync(); + await dbContext.SaveChangesAsync(ct); return group; } - public async Task GetByIdAsync(Guid id) + public async Task GetByIdAsync(Guid id, CancellationToken ct) { - return await dbContext.Groups.FindAsync(id); + return await dbContext.Groups.FindAsync([id], ct); } - public async Task UpdateAsync(Group group) + public async Task UpdateAsync(Group group, CancellationToken ct) { dbContext.Groups.Update(group); - await dbContext.SaveChangesAsync(); + await dbContext.SaveChangesAsync(ct); } - public async Task DeleteAsync(Guid id) + public async Task DeleteAsync(Guid id, CancellationToken ct) { - var group = await dbContext.Groups.FindAsync(id); + var group = await dbContext.Groups.FindAsync([id], ct); if (group is not null) { dbContext.Groups.Remove(group); - await dbContext.SaveChangesAsync(); + await dbContext.SaveChangesAsync(ct); } } + public async Task> GetAllAsync(CancellationToken ct) + { + return await dbContext.Groups.ToListAsync(ct); + } + public async Task AddChildrenToGroupAsync(Guid groupId, IEnumerable childIds, CancellationToken ct) { var group = await dbContext.Groups @@ -69,4 +74,11 @@ public async Task RemoveChildrenFromGroupAsync(Guid groupId, IEnumerable c await dbContext.SaveChangesAsync(ct); } + + public async Task> GetAllCohortsOfGroupAsync(Guid groupId, CancellationToken ct) + { + return await dbContext.Cohorts + .Where(c => c.GroupId == groupId) + .ToListAsync(ct); + } } \ No newline at end of file