diff --git a/src/Services/Scheduling/Api/Controllers/GroupsController.cs b/src/Services/Scheduling/Api/Controllers/GroupsController.cs index 440e8c66..19ca2422 100644 --- a/src/Services/Scheduling/Api/Controllers/GroupsController.cs +++ b/src/Services/Scheduling/Api/Controllers/GroupsController.cs @@ -1,17 +1,9 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using KDVManager.Services.Scheduling.Application.Features.Groups.Commands.AddGroup; +using KDVManager.Services.Scheduling.Application.Features.Groups.Commands.AddGroup; using KDVManager.Services.Scheduling.Application.Features.Groups.Queries.ListGroups; using MediatR; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; -using Microsoft.AspNetCore.Authorization; using KDVManager.Services.Scheduling.Application.Contracts.Pagination; using System.Net; -using KDVManager.Services.Scheduling.Application.Contracts.Persistence; -using System.Net.Mime; using KDVManager.Services.Scheduling.Application.Features.Groups.Commands.DeleteGroup; namespace KDVManager.Services.Scheduling.Api.Controllers; diff --git a/src/Services/Scheduling/Api/Controllers/ScheduleItemsController.cs b/src/Services/Scheduling/Api/Controllers/ScheduleItemsController.cs new file mode 100644 index 00000000..fa52ff84 --- /dev/null +++ b/src/Services/Scheduling/Api/Controllers/ScheduleItemsController.cs @@ -0,0 +1,40 @@ +using KDVManager.Services.Scheduling.Application.Features.Groups.Queries.ListGroups; +using MediatR; +using Microsoft.AspNetCore.Mvc; +using KDVManager.Services.Scheduling.Application.Contracts.Pagination; +using KDVManager.Services.Scheduling.Application.Features.ScheduleItems.Queries.ListScheduleItems; +using System.Net; +using KDVManager.Services.Scheduling.Application.Features.ScheduleItems.Commands.AddScheduleItem; + +namespace KDVManager.Services.Scheduling.Api.Controllers; + +[ApiController] +[Route("v1/[controller]")] +public class ScheduleItemsController : ControllerBase +{ + private readonly IMediator _mediator; + private readonly ILogger _logger; + + public ScheduleItemsController(IMediator mediator, ILogger logger) + { + _logger = logger; + _mediator = mediator; + } + + [HttpGet("", Name = "ListScheduleItems")] + public async Task>> ListScheduleItems([FromQuery] ListScheduleItemsQuery listScheduleItemsQuery) + { + var dtos = await _mediator.Send(listScheduleItemsQuery); + Response.Headers.Add("x-Total", dtos.TotalCount.ToString()); + return Ok(dtos); + } + + [HttpPost(Name = "AddScheduleItem")] + [ProducesResponseType(typeof(Guid), (int)HttpStatusCode.OK)] + [ProducesResponseType(typeof(UnprocessableEntityResponse), (int)HttpStatusCode.UnprocessableEntity)] + public async Task> AddScheduleItem([FromBody] AddScheduleItemCommand addScheduleItemCommand) + { + var id = await _mediator.Send(addScheduleItemCommand); + return Ok(id); + } +} diff --git a/src/Services/Scheduling/Api/Middleware/ExceptionHandlerMiddleware.cs b/src/Services/Scheduling/Api/Middleware/ExceptionHandlerMiddleware.cs index d4a12218..fc96bcb1 100644 --- a/src/Services/Scheduling/Api/Middleware/ExceptionHandlerMiddleware.cs +++ b/src/Services/Scheduling/Api/Middleware/ExceptionHandlerMiddleware.cs @@ -1,10 +1,6 @@ -using System; -using System.Net; -using System.Threading.Tasks; +using System.Net; using KDVManager.Services.Scheduling.Application.Exceptions; using System.Text.Json; -using System.Text.Json.Serialization; -using Microsoft.AspNetCore.Http; namespace KDVManager.Services.Scheduling.Api.Middleware; diff --git a/src/Services/Scheduling/Api/Middleware/ExceptionHandlerMiddlewareExtension.cs b/src/Services/Scheduling/Api/Middleware/ExceptionHandlerMiddlewareExtension.cs index c0a3ec32..5f372dd1 100644 --- a/src/Services/Scheduling/Api/Middleware/ExceptionHandlerMiddlewareExtension.cs +++ b/src/Services/Scheduling/Api/Middleware/ExceptionHandlerMiddlewareExtension.cs @@ -1,7 +1,4 @@ -using System; -using Microsoft.AspNetCore.Builder; - -namespace KDVManager.Services.Scheduling.Api.Middleware; +namespace KDVManager.Services.Scheduling.Api.Middleware; public static class ExceptionHandlerMiddlewareExtension { diff --git a/src/Services/Scheduling/Api/Program.cs b/src/Services/Scheduling/Api/Program.cs index 15d969f8..f8ec5240 100644 --- a/src/Services/Scheduling/Api/Program.cs +++ b/src/Services/Scheduling/Api/Program.cs @@ -1,6 +1,4 @@ using KDVManager.Services.Scheduling.Api.Middleware; -using KDVManager.Services.Scheduling.Application; -using KDVManager.Services.Scheduling.Infrastructure; var builder = WebApplication.CreateBuilder(args); diff --git a/src/Services/Scheduling/Api/Responses/UnprocessableEntityResponse.cs b/src/Services/Scheduling/Api/Responses/UnprocessableEntityResponse.cs index fdd90ce4..3c3edf82 100644 --- a/src/Services/Scheduling/Api/Responses/UnprocessableEntityResponse.cs +++ b/src/Services/Scheduling/Api/Responses/UnprocessableEntityResponse.cs @@ -1,5 +1,4 @@ using System.ComponentModel.DataAnnotations; -using KDVManager.Services.Scheduling.Application.Exceptions; using ValidationException = KDVManager.Services.Scheduling.Application.Exceptions.ValidationException; public class ValidationError diff --git a/src/Services/Scheduling/Api/Services/TenantService/TenantService.cs b/src/Services/Scheduling/Api/Services/TenantService/TenantService.cs index 699d94fc..8c2d25fc 100644 --- a/src/Services/Scheduling/Api/Services/TenantService/TenantService.cs +++ b/src/Services/Scheduling/Api/Services/TenantService/TenantService.cs @@ -1,7 +1,4 @@ -using System; using KDVManager.Services.Scheduling.Application.Contracts.Services; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Primitives; using KDVManager.Services.Scheduling.Application.Exceptions; namespace KDVManager.Services.Scheduling.Api.Services; diff --git a/src/Services/Scheduling/Application/Contracts/Persistence/IScheduleItemRepository.cs b/src/Services/Scheduling/Application/Contracts/Persistence/IScheduleItemRepository.cs new file mode 100644 index 00000000..3b23208f --- /dev/null +++ b/src/Services/Scheduling/Application/Contracts/Persistence/IScheduleItemRepository.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using KDVManager.Services.Scheduling.Application.Contracts.Persistence; +using KDVManager.Services.Scheduling.Domain.Entities; +using KDVManager.Services.Scheduling.Domain.Interfaces; + +namespace KDVManager.Services.Scheduling.Application.Contracts.Persistence; + +public interface IScheduleItemRepository : IAsyncRepository +{ + Task> GetScheduleItemsByChildIdAsync(Guid childId); +} diff --git a/src/Services/Scheduling/Application/Features/ScheduleItems/Commands/AddScheduleItem/AddScheduleItemCommand.cs b/src/Services/Scheduling/Application/Features/ScheduleItems/Commands/AddScheduleItem/AddScheduleItemCommand.cs new file mode 100644 index 00000000..859d68c9 --- /dev/null +++ b/src/Services/Scheduling/Application/Features/ScheduleItems/Commands/AddScheduleItem/AddScheduleItemCommand.cs @@ -0,0 +1,10 @@ +using System; +using MediatR; + +namespace KDVManager.Services.Scheduling.Application.Features.ScheduleItems.Commands.AddScheduleItem; + +public class AddScheduleItemCommand : IRequest +{ + public string Name { get; set; } + public Guid ChildId { get; set; } +} diff --git a/src/Services/Scheduling/Application/Features/ScheduleItems/Commands/AddScheduleItem/AddScheduleItemCommandHandler.cs b/src/Services/Scheduling/Application/Features/ScheduleItems/Commands/AddScheduleItem/AddScheduleItemCommandHandler.cs new file mode 100644 index 00000000..a3bfe0f8 --- /dev/null +++ b/src/Services/Scheduling/Application/Features/ScheduleItems/Commands/AddScheduleItem/AddScheduleItemCommandHandler.cs @@ -0,0 +1,37 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using AutoMapper; +using KDVManager.Services.Scheduling.Application.Contracts.Persistence; +using KDVManager.Services.Scheduling.Domain.Entities; +using MediatR; + +namespace KDVManager.Services.Scheduling.Application.Features.ScheduleItems.Commands.AddScheduleItem; + +public class AddScheduleItemCommandHandler : IRequestHandler +{ + private readonly IScheduleItemRepository _scheduleItemRepository; + private readonly IMapper _mapper; + + public AddScheduleItemCommandHandler(IScheduleItemRepository scheduleItemRepository, IMapper mapper) + { + _scheduleItemRepository = scheduleItemRepository; + _mapper = mapper; + } + + public async Task Handle(AddScheduleItemCommand request, CancellationToken cancellationToken) + { + var validator = new AddScheduleItemCommandValidator(_scheduleItemRepository); + var validationResult = await validator.ValidateAsync(request); + + if (!validationResult.IsValid) + throw new Exceptions.ValidationException(validationResult); + + var scheduleItem = _mapper.Map(request); + + scheduleItem = await _scheduleItemRepository.AddAsync(scheduleItem); + + return scheduleItem.Id; + } +} + diff --git a/src/Services/Scheduling/Application/Features/ScheduleItems/Commands/AddScheduleItem/AddScheduleItemCommandValidator.cs b/src/Services/Scheduling/Application/Features/ScheduleItems/Commands/AddScheduleItem/AddScheduleItemCommandValidator.cs new file mode 100644 index 00000000..481afc62 --- /dev/null +++ b/src/Services/Scheduling/Application/Features/ScheduleItems/Commands/AddScheduleItem/AddScheduleItemCommandValidator.cs @@ -0,0 +1,22 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using FluentValidation; +using KDVManager.Services.Scheduling.Application.Contracts.Persistence; + +namespace KDVManager.Services.Scheduling.Application.Features.ScheduleItems.Commands.AddScheduleItem; + +public class AddScheduleItemCommandValidator : AbstractValidator +{ + private readonly IScheduleItemRepository _scheduleItemRepository; + + public AddScheduleItemCommandValidator(IScheduleItemRepository scheduleItemRepository) + { + _scheduleItemRepository = scheduleItemRepository; + + RuleFor(AddScheduleItemCommand => AddScheduleItemCommand.Name) + .NotEmpty() + .NotNull() + .MaximumLength(25); + } +} diff --git a/src/Services/Scheduling/Application/Features/ScheduleItems/Queries/ListScheduleItems/ListScheduleItemsQuery.cs b/src/Services/Scheduling/Application/Features/ScheduleItems/Queries/ListScheduleItems/ListScheduleItemsQuery.cs new file mode 100644 index 00000000..d2e3fe4b --- /dev/null +++ b/src/Services/Scheduling/Application/Features/ScheduleItems/Queries/ListScheduleItems/ListScheduleItemsQuery.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using KDVManager.Services.Scheduling.Domain; +using KDVManager.Services.Scheduling.Application.Contracts.Pagination; +using MediatR; + +namespace KDVManager.Services.Scheduling.Application.Features.ScheduleItems.Queries.ListScheduleItems; + +public class ListScheduleItemsQuery : PageParameters, IRequest> +{ + public Guid ChildId { get; set; } +} + diff --git a/src/Services/Scheduling/Application/Features/ScheduleItems/Queries/ListScheduleItems/ListScheduleItemsQueryHandler.cs b/src/Services/Scheduling/Application/Features/ScheduleItems/Queries/ListScheduleItems/ListScheduleItemsQueryHandler.cs new file mode 100644 index 00000000..731a288c --- /dev/null +++ b/src/Services/Scheduling/Application/Features/ScheduleItems/Queries/ListScheduleItems/ListScheduleItemsQueryHandler.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using AutoMapper; +using KDVManager.Services.Scheduling.Application.Contracts.Persistence; +using KDVManager.Services.Scheduling.Application.Contracts.Pagination; +using KDVManager.Services.Scheduling.Domain.Entities; +using MediatR; + +namespace KDVManager.Services.Scheduling.Application.Features.ScheduleItems.Queries.ListScheduleItems; + +public class ListScheduleItemsQueryHandler : IRequestHandler> +{ + private readonly IScheduleItemRepository _scheduleItemRepository; + private readonly IMapper _mapper; + + public ListScheduleItemsQueryHandler(IMapper mapper, IScheduleItemRepository scheduleItemRepository) + { + _scheduleItemRepository = scheduleItemRepository; + _mapper = mapper; + } + + public async Task> Handle(ListScheduleItemsQuery request, CancellationToken cancellationToken) + { + var scheduleItems = await _scheduleItemRepository.GetScheduleItemsByChildIdAsync(request.ChildId); + + + List scheduleItemListVMs = _mapper.Map>(scheduleItems); + + return new List(scheduleItemListVMs); + } +} + diff --git a/src/Services/Scheduling/Application/Features/ScheduleItems/Queries/ListScheduleItems/ScheduleItemListVM.cs b/src/Services/Scheduling/Application/Features/ScheduleItems/Queries/ListScheduleItems/ScheduleItemListVM.cs new file mode 100644 index 00000000..dc36c209 --- /dev/null +++ b/src/Services/Scheduling/Application/Features/ScheduleItems/Queries/ListScheduleItems/ScheduleItemListVM.cs @@ -0,0 +1,13 @@ +using System; + +namespace KDVManager.Services.Scheduling.Application.Features.ScheduleItems.Queries.ListScheduleItems; + +public class ScheduleItemListVM +{ + public Guid Id { get; set; } + public Guid ChildId { get; set; } + public string Title { get; set; } + public DateTime StartDate { get; set; } + public DateTime? EndDate { get; set; } +} + diff --git a/src/Services/Scheduling/Application/Profiles/MappingProfile.cs b/src/Services/Scheduling/Application/Profiles/MappingProfile.cs index a09ecef8..c666f1e8 100644 --- a/src/Services/Scheduling/Application/Profiles/MappingProfile.cs +++ b/src/Services/Scheduling/Application/Profiles/MappingProfile.cs @@ -3,6 +3,7 @@ using KDVManager.Services.Scheduling.Application.Contracts.Pagination; using KDVManager.Services.Scheduling.Application.Features.Groups.Commands.AddGroup; using KDVManager.Services.Scheduling.Application.Features.Groups.Queries.ListGroups; +using KDVManager.Services.Scheduling.Application.Features.ScheduleItems.Queries.ListScheduleItems; using KDVManager.Services.Scheduling.Domain.Entities; namespace KDVManager.Services.Scheduling.Application.Profiles; @@ -14,6 +15,9 @@ public MappingProfile() CreateMap(); CreateMap().ReverseMap(); CreateMap(); + + CreateMap(); + CreateMap(); } } diff --git a/src/Services/Scheduling/Domain/Entities/RecurringSchedulePattern.cs b/src/Services/Scheduling/Domain/Entities/RecurringSchedulePattern.cs new file mode 100644 index 00000000..51720679 --- /dev/null +++ b/src/Services/Scheduling/Domain/Entities/RecurringSchedulePattern.cs @@ -0,0 +1,11 @@ +using System; +public class RecurringSchedulePattern +{ + public Guid Id { get; set; } + public Guid TenantId { get; set; } + public Guid ScheduleItemId { get; set; } + public DayOfWeek StartDay { get; set; } + public TimeSpan StartTime { get; set; } + public DayOfWeek EndDay { get; set; } + public TimeSpan EndTime { get; set; } +} \ No newline at end of file diff --git a/src/Services/Scheduling/Domain/Entities/ScheduleItem.cs b/src/Services/Scheduling/Domain/Entities/ScheduleItem.cs new file mode 100644 index 00000000..49b3e1f5 --- /dev/null +++ b/src/Services/Scheduling/Domain/Entities/ScheduleItem.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +public class ScheduleItem +{ + public Guid Id { get; set; } + public Guid TenantId { get; set; } + public Guid ChildId { get; set; } + public string Title { get; set; } + public DateTime StartDate { get; set; } + public DateTime? EndDate { get; set; } + public ICollection recurringSchedulePatterns { get; set; } +} \ No newline at end of file diff --git a/src/Services/Scheduling/Infrastructure/ConfigureServices.cs b/src/Services/Scheduling/Infrastructure/ConfigureServices.cs index 9be337af..6bce9506 100644 --- a/src/Services/Scheduling/Infrastructure/ConfigureServices.cs +++ b/src/Services/Scheduling/Infrastructure/ConfigureServices.cs @@ -20,6 +20,8 @@ public static IServiceCollection AddInfrastructureServices(this IServiceCollecti services.AddScoped(); + services.AddScoped(); + return services; } } diff --git a/src/Services/Scheduling/Infrastructure/MigrationDbContext.cs b/src/Services/Scheduling/Infrastructure/MigrationDbContext.cs index ec703281..3ec8854f 100644 --- a/src/Services/Scheduling/Infrastructure/MigrationDbContext.cs +++ b/src/Services/Scheduling/Infrastructure/MigrationDbContext.cs @@ -12,6 +12,8 @@ public MigrationDbContext(DbContextOptions options) : base(o } public DbSet Groups { get; set; } + public DbSet ScheduleItems { get; set; } + public DbSet RecurringSchedulePatterns { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { diff --git a/src/Services/Scheduling/Infrastructure/Migrations/20240625192040_AddSchedulingItems.Designer.cs b/src/Services/Scheduling/Infrastructure/Migrations/20240625192040_AddSchedulingItems.Designer.cs new file mode 100644 index 00000000..888f021b --- /dev/null +++ b/src/Services/Scheduling/Infrastructure/Migrations/20240625192040_AddSchedulingItems.Designer.cs @@ -0,0 +1,119 @@ +// +using System; +using KDVManager.Services.Scheduling.Infrastructure; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Infrastructure.Migrations +{ + [DbContext(typeof(MigrationDbContext))] + [Migration("20240625192040_AddSchedulingItems")] + partial class AddSchedulingItems + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("KDVManager.Services.Scheduling.Domain.Entities.Group", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("RecurringSchedulePattern", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("EndDay") + .HasColumnType("integer"); + + b.Property("EndTime") + .HasColumnType("interval"); + + b.Property("ScheduleItemId") + .HasColumnType("uuid"); + + b.Property("StartDay") + .HasColumnType("integer"); + + b.Property("StartTime") + .HasColumnType("interval"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ScheduleItemId"); + + b.ToTable("RecurringSchedulePatterns"); + }); + + modelBuilder.Entity("ScheduleItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ChildId") + .HasColumnType("uuid"); + + b.Property("EndDate") + .HasColumnType("timestamp with time zone"); + + b.Property("StartDate") + .HasColumnType("timestamp with time zone"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("Title") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("ScheduleItems"); + }); + + modelBuilder.Entity("RecurringSchedulePattern", b => + { + b.HasOne("ScheduleItem", null) + .WithMany("recurringSchedulePatterns") + .HasForeignKey("ScheduleItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ScheduleItem", b => + { + b.Navigation("recurringSchedulePatterns"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Services/Scheduling/Infrastructure/Migrations/20240625192040_AddSchedulingItems.cs b/src/Services/Scheduling/Infrastructure/Migrations/20240625192040_AddSchedulingItems.cs new file mode 100644 index 00000000..e8041720 --- /dev/null +++ b/src/Services/Scheduling/Infrastructure/Migrations/20240625192040_AddSchedulingItems.cs @@ -0,0 +1,69 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Infrastructure.Migrations +{ + /// + public partial class AddSchedulingItems : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "ScheduleItems", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + TenantId = table.Column(type: "uuid", nullable: false), + ChildId = table.Column(type: "uuid", nullable: false), + Title = table.Column(type: "text", nullable: true), + StartDate = table.Column(type: "timestamp with time zone", nullable: false), + EndDate = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_ScheduleItems", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "RecurringSchedulePatterns", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + TenantId = table.Column(type: "uuid", nullable: false), + ScheduleItemId = table.Column(type: "uuid", nullable: false), + StartDay = table.Column(type: "integer", nullable: false), + StartTime = table.Column(type: "interval", nullable: false), + EndDay = table.Column(type: "integer", nullable: false), + EndTime = table.Column(type: "interval", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_RecurringSchedulePatterns", x => x.Id); + table.ForeignKey( + name: "FK_RecurringSchedulePatterns_ScheduleItems_ScheduleItemId", + column: x => x.ScheduleItemId, + principalTable: "ScheduleItems", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_RecurringSchedulePatterns_ScheduleItemId", + table: "RecurringSchedulePatterns", + column: "ScheduleItemId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "RecurringSchedulePatterns"); + + migrationBuilder.DropTable( + name: "ScheduleItems"); + } + } +} diff --git a/src/Services/Scheduling/Infrastructure/Migrations/MigrationDbContextModelSnapshot.cs b/src/Services/Scheduling/Infrastructure/Migrations/MigrationDbContextModelSnapshot.cs index ffae097c..1296691d 100644 --- a/src/Services/Scheduling/Infrastructure/Migrations/MigrationDbContextModelSnapshot.cs +++ b/src/Services/Scheduling/Infrastructure/Migrations/MigrationDbContextModelSnapshot.cs @@ -39,6 +39,77 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("Groups"); }); + + modelBuilder.Entity("RecurringSchedulePattern", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("EndDay") + .HasColumnType("integer"); + + b.Property("EndTime") + .HasColumnType("interval"); + + b.Property("ScheduleItemId") + .HasColumnType("uuid"); + + b.Property("StartDay") + .HasColumnType("integer"); + + b.Property("StartTime") + .HasColumnType("interval"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ScheduleItemId"); + + b.ToTable("RecurringSchedulePatterns"); + }); + + modelBuilder.Entity("ScheduleItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ChildId") + .HasColumnType("uuid"); + + b.Property("EndDate") + .HasColumnType("timestamp with time zone"); + + b.Property("StartDate") + .HasColumnType("timestamp with time zone"); + + b.Property("TenantId") + .HasColumnType("uuid"); + + b.Property("Title") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("ScheduleItems"); + }); + + modelBuilder.Entity("RecurringSchedulePattern", b => + { + b.HasOne("ScheduleItem", null) + .WithMany("recurringSchedulePatterns") + .HasForeignKey("ScheduleItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ScheduleItem", b => + { + b.Navigation("recurringSchedulePatterns"); + }); #pragma warning restore 612, 618 } } diff --git a/src/Services/Scheduling/Infrastructure/Repositories/ScheduleItemRepository.cs b/src/Services/Scheduling/Infrastructure/Repositories/ScheduleItemRepository.cs new file mode 100644 index 00000000..54a5ee32 --- /dev/null +++ b/src/Services/Scheduling/Infrastructure/Repositories/ScheduleItemRepository.cs @@ -0,0 +1,24 @@ +using System; +using System.Linq; +using System.Collections.Generic; +using System.Threading.Tasks; +using KDVManager.Services.Scheduling.Application.Contracts.Persistence; +using KDVManager.Services.Scheduling.Domain.Entities; +using KDVManager.Services.Scheduling.Domain.Interfaces; +using Microsoft.EntityFrameworkCore; + +namespace KDVManager.Services.Scheduling.Infrastructure.Repositories; + +public class ScheduleItemRepository : BaseRepository, IScheduleItemRepository +{ + public ScheduleItemRepository(SchedulingDbContext dbContext) : base(dbContext) + { + } + + public async Task> GetScheduleItemsByChildIdAsync(Guid childId) + { + return await _dbContext.ScheduleItems + .Where(si => si.ChildId == childId) + .ToListAsync(); + } +} diff --git a/src/Services/Scheduling/Infrastructure/SchedulingDbContext.cs b/src/Services/Scheduling/Infrastructure/SchedulingDbContext.cs index 32320c96..01241cd6 100644 --- a/src/Services/Scheduling/Infrastructure/SchedulingDbContext.cs +++ b/src/Services/Scheduling/Infrastructure/SchedulingDbContext.cs @@ -18,11 +18,15 @@ public SchedulingDbContext(DbContextOptions options, ITenan } public DbSet Groups { get; set; } + public DbSet ScheduleItems { get; set; } + public DbSet RecurringSchedulePatterns { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.Entity().HasQueryFilter(a => a.TenantId == _tenantService.Tenant); + modelBuilder.Entity().HasQueryFilter(a => a.TenantId == _tenantService.Tenant); + modelBuilder.Entity().HasQueryFilter(a => a.TenantId == _tenantService.Tenant); modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); } diff --git a/src/web/output.openapi.json b/src/web/output.openapi.json index b530ed21..1334fc7b 100644 --- a/src/web/output.openapi.json +++ b/src/web/output.openapi.json @@ -418,6 +418,137 @@ } } } + }, + "/scheduling/v1/scheduleitems": { + "get": { + "tags": ["ScheduleItems"], + "operationId": "ListScheduleItems", + "parameters": [ + { + "name": "ChildId", + "in": "query", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "PageNumber", + "in": "query", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "PageSize", + "in": "query", + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ScheduleItemListVM" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ScheduleItemListVM" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ScheduleItemListVM" + } + } + } + } + } + } + }, + "post": { + "tags": ["ScheduleItems"], + "operationId": "AddScheduleItem", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AddScheduleItemCommand" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/AddScheduleItemCommand" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/AddScheduleItemCommand" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "text/plain": { + "schema": { + "type": "string", + "format": "uuid" + } + }, + "application/json": { + "schema": { + "type": "string", + "format": "uuid" + } + }, + "text/json": { + "schema": { + "type": "string", + "format": "uuid" + } + } + } + }, + "422": { + "description": "Client Error", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/UnprocessableEntityResponse" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnprocessableEntityResponse" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/UnprocessableEntityResponse" + } + } + } + } + } + } } }, "components": { @@ -530,6 +661,20 @@ }, "additionalProperties": false }, + "AddScheduleItemCommand": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true + }, + "childId": { + "type": "string", + "format": "uuid" + } + }, + "additionalProperties": false + }, "GroupListVM": { "type": "object", "properties": { @@ -571,6 +716,33 @@ }, "additionalProperties": {} }, + "ScheduleItemListVM": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "childId": { + "type": "string", + "format": "uuid" + }, + "title": { + "type": "string", + "nullable": true + }, + "startDate": { + "type": "string", + "format": "date-time" + }, + "endDate": { + "type": "string", + "format": "date-time", + "nullable": true + } + }, + "additionalProperties": false + }, "UnprocessableEntityResponse": { "required": ["errors", "status"], "type": "object", diff --git a/src/web/src/api/endpoints/schedule-items/schedule-items.ts b/src/web/src/api/endpoints/schedule-items/schedule-items.ts new file mode 100644 index 00000000..031cbd00 --- /dev/null +++ b/src/web/src/api/endpoints/schedule-items/schedule-items.ts @@ -0,0 +1,171 @@ +/** + * Generated by orval v6.28.2 🍺 + * Do not edit manually. + * KDVManager CRM API + * OpenAPI spec version: v1 + */ +import { useMutation, useQuery } from "@tanstack/react-query"; +import type { + MutationFunction, + QueryFunction, + QueryKey, + UseMutationOptions, + UseMutationResult, + UseQueryOptions, + UseQueryResult, +} from "@tanstack/react-query"; +import type { AddScheduleItemCommand } from "../../models/addScheduleItemCommand"; +import type { ListScheduleItemsParams } from "../../models/listScheduleItemsParams"; +import type { ScheduleItemListVM } from "../../models/scheduleItemListVM"; +import type { UnprocessableEntityResponse } from "../../models/unprocessableEntityResponse"; +import { useExecuteFetch } from "../../mutator/useExecuteFetch"; + +export const useListScheduleItemsHook = () => { + const listScheduleItems = useExecuteFetch(); + + return (params?: ListScheduleItemsParams, signal?: AbortSignal) => { + return listScheduleItems({ + url: `/scheduling/v1/scheduleitems`, + method: "GET", + params, + signal, + }); + }; +}; + +export const getListScheduleItemsQueryKey = (params?: ListScheduleItemsParams) => { + return [`/scheduling/v1/scheduleitems`, ...(params ? [params] : [])] as const; +}; + +export const useListScheduleItemsQueryOptions = < + TData = Awaited>>, + TError = unknown, +>( + params?: ListScheduleItemsParams, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>>, + TError, + TData + > + >; + }, +) => { + const { query: queryOptions } = options ?? {}; + + const queryKey = queryOptions?.queryKey ?? getListScheduleItemsQueryKey(params); + + const listScheduleItems = useListScheduleItemsHook(); + + const queryFn: QueryFunction< + Awaited>> + > = ({ signal }) => listScheduleItems(params, signal); + + return { queryKey, queryFn, ...queryOptions } as UseQueryOptions< + Awaited>>, + TError, + TData + > & { queryKey: QueryKey }; +}; + +export type ListScheduleItemsQueryResult = NonNullable< + Awaited>> +>; +export type ListScheduleItemsQueryError = unknown; + +export const useListScheduleItems = < + TData = Awaited>>, + TError = unknown, +>( + params?: ListScheduleItemsParams, + options?: { + query?: Partial< + UseQueryOptions< + Awaited>>, + TError, + TData + > + >; + }, +): UseQueryResult & { queryKey: QueryKey } => { + const queryOptions = useListScheduleItemsQueryOptions(params, options); + + const query = useQuery(queryOptions) as UseQueryResult & { queryKey: QueryKey }; + + query.queryKey = queryOptions.queryKey; + + return query; +}; + +export const useAddScheduleItemHook = () => { + const addScheduleItem = useExecuteFetch(); + + return (addScheduleItemCommand: AddScheduleItemCommand) => { + return addScheduleItem({ + url: `/scheduling/v1/scheduleitems`, + method: "POST", + headers: { "Content-Type": "application/json" }, + data: addScheduleItemCommand, + }); + }; +}; + +export const useAddScheduleItemMutationOptions = < + TError = UnprocessableEntityResponse, + TContext = unknown, +>(options?: { + mutation?: UseMutationOptions< + Awaited>>, + TError, + { data: AddScheduleItemCommand }, + TContext + >; +}): UseMutationOptions< + Awaited>>, + TError, + { data: AddScheduleItemCommand }, + TContext +> => { + const { mutation: mutationOptions } = options ?? {}; + + const addScheduleItem = useAddScheduleItemHook(); + + const mutationFn: MutationFunction< + Awaited>>, + { data: AddScheduleItemCommand } + > = (props) => { + const { data } = props ?? {}; + + return addScheduleItem(data); + }; + + return { mutationFn, ...mutationOptions }; +}; + +export type AddScheduleItemMutationResult = NonNullable< + Awaited>> +>; +export type AddScheduleItemMutationBody = AddScheduleItemCommand; +export type AddScheduleItemMutationError = UnprocessableEntityResponse; + +export const useAddScheduleItem = < + TError = UnprocessableEntityResponse, + TContext = unknown, +>(options?: { + mutation?: UseMutationOptions< + Awaited>>, + TError, + { data: AddScheduleItemCommand }, + TContext + >; +}): UseMutationResult< + Awaited>>, + TError, + { data: AddScheduleItemCommand }, + TContext +> => { + const mutationOptions = useAddScheduleItemMutationOptions(options); + + return useMutation(mutationOptions); +}; diff --git a/src/web/src/api/models/addScheduleItemCommand.ts b/src/web/src/api/models/addScheduleItemCommand.ts new file mode 100644 index 00000000..c79d749e --- /dev/null +++ b/src/web/src/api/models/addScheduleItemCommand.ts @@ -0,0 +1,12 @@ +/** + * Generated by orval v6.28.2 🍺 + * Do not edit manually. + * KDVManager CRM API + * OpenAPI spec version: v1 + */ + +export type AddScheduleItemCommand = { + childId?: string; + /** @nullable */ + name?: string | null; +}; diff --git a/src/web/src/api/models/listScheduleItemsParams.ts b/src/web/src/api/models/listScheduleItemsParams.ts new file mode 100644 index 00000000..59e3a38e --- /dev/null +++ b/src/web/src/api/models/listScheduleItemsParams.ts @@ -0,0 +1,12 @@ +/** + * Generated by orval v6.28.2 🍺 + * Do not edit manually. + * KDVManager CRM API + * OpenAPI spec version: v1 + */ + +export type ListScheduleItemsParams = { + ChildId?: string; + PageNumber?: number; + PageSize?: number; +}; diff --git a/src/web/src/api/models/scheduleItemListVM.ts b/src/web/src/api/models/scheduleItemListVM.ts new file mode 100644 index 00000000..dec368eb --- /dev/null +++ b/src/web/src/api/models/scheduleItemListVM.ts @@ -0,0 +1,16 @@ +/** + * Generated by orval v6.28.2 🍺 + * Do not edit manually. + * KDVManager CRM API + * OpenAPI spec version: v1 + */ + +export type ScheduleItemListVM = { + childId?: string; + /** @nullable */ + endDate?: string | null; + id?: string; + startDate?: string; + /** @nullable */ + title?: string | null; +};