diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a4f9c1f..bc24f51 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -27,7 +27,7 @@ jobs: # Launch and prepare MySQL server sudo systemctl start mysql.service - dotnet run -c Release --no-build --project server/src/Korga.Server -- database create --populate + dotnet run -c Release --no-build --project server/src/Korga.Server -- database create - name: Test run: dotnet test -c Release --no-build --verbosity normal diff --git a/README.md b/README.md index 734bd7e..aff7d24 100644 --- a/README.md +++ b/README.md @@ -20,14 +20,6 @@ There is no Web UI available yet to manage distribution lists so you must stick This command creates a distribution list _kids@example.org_ which forwards emails to every member of group #137. -### Event registration - -![Three screenshots of Korga's event registration](docs/assets/event_registration_overview.png) - -An event registration with multiple programs for each event. The list of participants is public as well as the possibility to delete registrations. - -Currently, there is neither an API endpoint nor a graphical user interface available to edit events and programs. Instead you have to write SQL queries. - ## Installation The only officially supported distribution are Docker containers. An official image is available at [daniellerch/korga](https://hub.docker.com/r/daniellerch/korga). diff --git a/docs/assets/event_registration_overview.png b/docs/assets/event_registration_overview.png deleted file mode 100644 index e220285..0000000 Binary files a/docs/assets/event_registration_overview.png and /dev/null differ diff --git a/server/src/Korga.Core/DatabaseContext.cs b/server/src/Korga.Core/DatabaseContext.cs index 4046253..dd5a49b 100644 --- a/server/src/Korga.Core/DatabaseContext.cs +++ b/server/src/Korga.Core/DatabaseContext.cs @@ -1,7 +1,6 @@ using Korga.ChurchTools.Entities; using Korga.EmailDelivery.Entities; using Korga.EmailRelay.Entities; -using Korga.EventRegistration.Entities; using Microsoft.EntityFrameworkCore; namespace Korga; @@ -15,10 +14,6 @@ namespace Korga; /// public sealed class DatabaseContext : DbContext { - public DbSet Events => Set(); - public DbSet EventPrograms => Set(); - public DbSet EventParticipants => Set(); - public DbSet People => Set(); public DbSet Groups => Set(); public DbSet GroupMembers => Set(); @@ -43,8 +38,6 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); - CreateEvents(modelBuilder); - CreateChurchTools(modelBuilder); CreateEmailRelay(modelBuilder); @@ -52,20 +45,6 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) CreateEmailDelivery(modelBuilder); } - private void CreateEvents(ModelBuilder modelBuilder) - { - var @event = modelBuilder.Entity(); - @event.HasKey(e => e.Id); - - var program = modelBuilder.Entity(); - program.HasKey(p => p.Id); - program.HasOne(p => p.Event).WithMany().HasForeignKey(p => p.EventId); - - var participant = modelBuilder.Entity(); - participant.HasKey(p => p.Id); - participant.HasOne(p => p.Program).WithMany(p => p.Participants).HasForeignKey(p => p.ProgramId); - } - private void CreateChurchTools(ModelBuilder modelBuilder) { var person = modelBuilder.Entity(); diff --git a/server/src/Korga.Core/EventRegistration/Entities/Event.cs b/server/src/Korga.Core/EventRegistration/Entities/Event.cs deleted file mode 100644 index 4dfc18a..0000000 --- a/server/src/Korga.Core/EventRegistration/Entities/Event.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Korga.EventRegistration.Entities; - -public class Event -{ - public Event(string name) - { - Name = name; - } - - public long Id { get; set; } - public string Name { get; set; } -} diff --git a/server/src/Korga.Core/EventRegistration/Entities/EventParticipant.cs b/server/src/Korga.Core/EventRegistration/Entities/EventParticipant.cs deleted file mode 100644 index 239cd2b..0000000 --- a/server/src/Korga.Core/EventRegistration/Entities/EventParticipant.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Korga.EventRegistration.Entities; - -public class EventParticipant -{ - public EventParticipant(string givenName, string familyName) - { - GivenName = givenName; - FamilyName = familyName; - } - - public long Id { get; set; } - - public long ProgramId { get; set; } - public EventProgram? Program { get; set; } - - public string GivenName { get; set; } - public string FamilyName { get; set; } -} diff --git a/server/src/Korga.Core/EventRegistration/Entities/EventProgram.cs b/server/src/Korga.Core/EventRegistration/Entities/EventProgram.cs deleted file mode 100644 index 8a7b26c..0000000 --- a/server/src/Korga.Core/EventRegistration/Entities/EventProgram.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Collections.Generic; - -namespace Korga.EventRegistration.Entities; - -public class EventProgram -{ - public EventProgram(string name) - { - Name = name; - } - - public long Id { get; set; } - - public long EventId { get; set; } - public Event? Event { get; set; } - - public string Name { get; set; } - public int Limit { get; set; } - - public IEnumerable? Participants { get; set; } -} diff --git a/server/src/Korga.Server/Commands/DatabaseCommand.cs b/server/src/Korga.Server/Commands/DatabaseCommand.cs index f8fba72..41afb0c 100644 --- a/server/src/Korga.Server/Commands/DatabaseCommand.cs +++ b/server/src/Korga.Server/Commands/DatabaseCommand.cs @@ -1,5 +1,4 @@ -using Korga.EventRegistration.Entities; -using McMaster.Extensions.CommandLineUtils; +using McMaster.Extensions.CommandLineUtils; using Microsoft.EntityFrameworkCore; using System.Threading.Tasks; @@ -9,7 +8,7 @@ namespace Korga.Server.Commands; [Command("database")] -[Subcommand(typeof(Migrate), typeof(Create), typeof(Delete), typeof(Populate))] +[Subcommand(typeof(Migrate), typeof(Create), typeof(Delete))] public class DatabaseCommand { private const string populateDescription = "Fills an existing database with example data for testing."; @@ -35,16 +34,11 @@ public class Create [Option(Description = "Forces a recreation of the database")] public bool Force { get; set; } - [Option(Description = populateDescription)] - public bool Populate { get; set; } - private async Task OnExecute(CommandLineApplication app, DatabaseContext database) { if (Force) await DeleteDatabase(app, database); await database.Database.EnsureCreatedAsync(); - - if (Populate) await PopulateDatabase(database); } } @@ -54,12 +48,6 @@ public class Delete private Task OnExecute(CommandLineApplication app, DatabaseContext database) => DeleteDatabase(app, database); } - [Command("populate", Description = populateDescription)] - public class Populate - { - private Task OnExecute(DatabaseContext database) => PopulateDatabase(database); - } - private static async Task DeleteDatabase(CommandLineApplication app, DatabaseContext database) { if (Prompt.GetYesNo("Do you really want to delete the Korga database?", false)) @@ -68,23 +56,4 @@ private static async Task DeleteDatabase(CommandLineApplication app, DatabaseCon if (!deleted) app.Error.WriteLine("Notice: No database found to delete."); } } - - private static async Task PopulateDatabase(DatabaseContext database) - { - // Create two services as events - var service10 = new Event("Gottesdienst am 06.03. um 10 Uhr"); - var service12 = new Event("Gottesdienst am 06.03. um 12 Uhr"); - database.Events.AddRange(service10, service12); - await database.SaveChangesAsync(); - - // Create children's ministry as programs - var program10_0 = new EventProgram("Gottesdienst") { EventId = service10.Id, Limit = 65 }; - var program10_1 = new EventProgram("Kükennest (0-3 Jahre)") { EventId = service10.Id, Limit = 5 }; - var program10_2 = new EventProgram("Kindergartenkinder") { EventId = service10.Id, Limit = 12 }; - var program10_3 = new EventProgram("Grundschulkinder") { EventId = service10.Id, Limit = 12 }; - var program10_4 = new EventProgram("Weiterführende Schule") { EventId = service10.Id, Limit = 12 }; - var program12_0 = new EventProgram("Gottesdienst") { EventId = service12.Id, Limit = 65 }; - database.EventPrograms.AddRange(program10_0, program10_1, program10_2, program10_3, program10_4, program12_0); - await database.SaveChangesAsync(); - } } diff --git a/server/src/Korga.Server/Controllers/EventController.cs b/server/src/Korga.Server/Controllers/EventController.cs deleted file mode 100644 index 1877ff2..0000000 --- a/server/src/Korga.Server/Controllers/EventController.cs +++ /dev/null @@ -1,114 +0,0 @@ -using Korga.EventRegistration.Entities; -using Korga.Server.Models.Json; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Korga.Server.Controllers; - -[ApiController] -public class EventController : ControllerBase -{ - private readonly DatabaseContext database; - private readonly ILogger logger; - - public EventController(DatabaseContext database, ILogger logger) - { - this.database = database; - this.logger = logger; - } - - [HttpGet("~/api/events")] - [ProducesResponseType(typeof(EventResponse[]), StatusCodes.Status200OK)] - public async Task ListEvents() - { - // EF Core cannot translate complex group joins in SQL - var events = await - (from e in database.Events - join p in database.EventPrograms on e.Id equals p.EventId - select new { Event = e, Program = p, Count = p.Participants!.Count() }) - .ToListAsync(); - - // Grouping operation is performed client-sided - return new JsonResult(events - .GroupBy(x => x.Event) - .Select(x => new EventResponse(x.Key, x - .Select(y => new EventResponse.Program(y.Program, y.Count)) - .OrderBy(y => y.Id) - .ToList())) - .OrderBy(x=> x.Id) - .ToList()); - } - - [HttpGet("~/api/event/{id}")] - [ProducesResponseType(typeof(EventResponse2), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(void), StatusCodes.Status404NotFound)] - public async Task GetEvent(long id) - { - Event? @event = await database.Events.SingleOrDefaultAsync(x => x.Id == id); - if (@event is null) return StatusCode(StatusCodes.Status404NotFound); - - // EF cannot translate simple groups joins either so use a LEFT JOIN - var programs = await - (from p in database.EventPrograms - where p.EventId == id - join pa in database.EventParticipants on p.Id equals pa.ProgramId into grouping - from pa in grouping.DefaultIfEmpty() - select new { Program = p, Participant = pa }) - .ToListAsync(); - - // Grouping operation is performed client-sided respecting null values for programs without participants - return new JsonResult(new EventResponse2(@event, programs - .GroupBy(x => x.Program) - .Select(x => new EventResponse2.Program(x.Key, x - .Where(y => y.Participant is not null) - .Select(y => new EventResponse2.Participant(y.Participant)) - .OrderByDescending(y => y.Id) - .ToList())) - .OrderBy(x => x.Id) - .ToList())); - } - - [HttpPost("~/api/events/register")] - [ProducesResponseType(StatusCodes.Status204NoContent)] - [ProducesResponseType(typeof(void), StatusCodes.Status400BadRequest)] - [ProducesResponseType(typeof(void), StatusCodes.Status404NotFound)] - [ProducesResponseType(typeof(void), StatusCodes.Status409Conflict)] - public async Task Register([FromBody] EventRegistrationRequest request) - { - if (!ModelState.IsValid) return StatusCode(StatusCodes.Status400BadRequest); - - var p = await database.EventPrograms - .Where(x => x.Id == request.ProgramId) - .Select(x => new { Program = x, Count = x.Participants!.Count() }) - .SingleOrDefaultAsync(); - if (p is null) return StatusCode(StatusCodes.Status404NotFound); - if (p.Count >= p.Program.Limit) return StatusCode(StatusCodes.Status409Conflict); - - var participant = new EventParticipant(request.GivenName, request.FamilyName) { ProgramId = request.ProgramId }; - database.EventParticipants.Add(participant); - await database.SaveChangesAsync(); - - return StatusCode(StatusCodes.Status204NoContent); - } - - [HttpDelete("~/api/events/participant/{id}")] - [ProducesResponseType(StatusCodes.Status204NoContent)] - [ProducesResponseType(typeof(void), StatusCodes.Status404NotFound)] - public async Task DeleteRegistration(long id) - { - EventParticipant? participant = await database.EventParticipants.SingleOrDefaultAsync(x => x.Id == id); - if (participant is null) return StatusCode(StatusCodes.Status404NotFound); - - database.EventParticipants.Remove(participant); - await database.SaveChangesAsync(); - - logger.LogInformation("Deleted participant {givenName} {familyName} from program {programId}", participant.GivenName, participant.FamilyName, participant.ProgramId); - - return StatusCode(StatusCodes.Status204NoContent); - } -} diff --git a/server/src/Korga.Server/Migrations/20231212132844_RemoveEventRegistration.Designer.cs b/server/src/Korga.Server/Migrations/20231212132844_RemoveEventRegistration.Designer.cs new file mode 100644 index 0000000..6b588c9 --- /dev/null +++ b/server/src/Korga.Server/Migrations/20231212132844_RemoveEventRegistration.Designer.cs @@ -0,0 +1,581 @@ +// +using System; +using Korga; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Korga.Server.Migrations +{ + [DbContext(typeof(DatabaseContext))] + [Migration("20231212132844_RemoveEventRegistration")] + partial class RemoveEventRegistration + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.13") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("Korga.ChurchTools.Entities.Department", b => + { + b.Property("Id") + .HasColumnType("int"); + + b.Property("DeletionTime") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("Departments"); + }); + + modelBuilder.Entity("Korga.ChurchTools.Entities.DepartmentMember", b => + { + b.Property("PersonId") + .HasColumnType("int"); + + b.Property("DepartmentId") + .HasColumnType("int"); + + b.HasKey("PersonId", "DepartmentId"); + + b.HasIndex("DepartmentId"); + + b.ToTable("DepartmentMembers"); + }); + + modelBuilder.Entity("Korga.ChurchTools.Entities.Group", b => + { + b.Property("Id") + .HasColumnType("int"); + + b.Property("DeletionTime") + .HasColumnType("datetime(6)"); + + b.Property("GroupStatusId") + .HasColumnType("int"); + + b.Property("GroupTypeId") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("GroupStatusId"); + + b.HasIndex("GroupTypeId"); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("Korga.ChurchTools.Entities.GroupMember", b => + { + b.Property("PersonId") + .HasColumnType("int"); + + b.Property("GroupId") + .HasColumnType("int"); + + b.Property("GroupRoleId") + .HasColumnType("int"); + + b.HasKey("PersonId", "GroupId"); + + b.HasIndex("GroupId"); + + b.HasIndex("GroupRoleId"); + + b.ToTable("GroupMembers"); + }); + + modelBuilder.Entity("Korga.ChurchTools.Entities.GroupRole", b => + { + b.Property("Id") + .HasColumnType("int"); + + b.Property("DeletionTime") + .HasColumnType("datetime(6)"); + + b.Property("GroupTypeId") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("GroupTypeId"); + + b.ToTable("GroupRoles"); + }); + + modelBuilder.Entity("Korga.ChurchTools.Entities.GroupStatus", b => + { + b.Property("Id") + .HasColumnType("int"); + + b.Property("DeletionTime") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("GroupStatuses"); + }); + + modelBuilder.Entity("Korga.ChurchTools.Entities.GroupType", b => + { + b.Property("Id") + .HasColumnType("int"); + + b.Property("DeletionTime") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("GroupTypes"); + }); + + modelBuilder.Entity("Korga.ChurchTools.Entities.Person", b => + { + b.Property("Id") + .HasColumnType("int"); + + b.Property("DeletionTime") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("FirstName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("LastName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StatusId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("StatusId"); + + b.ToTable("People"); + }); + + modelBuilder.Entity("Korga.ChurchTools.Entities.Status", b => + { + b.Property("Id") + .HasColumnType("int"); + + b.Property("DeletionTime") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("Status"); + }); + + modelBuilder.Entity("Korga.EmailDelivery.Entities.OutboxEmail", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("Content") + .IsRequired() + .HasColumnType("longblob"); + + b.Property("EmailAddress") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("InboxEmailId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("InboxEmailId"); + + b.ToTable("OutboxEmails"); + }); + + modelBuilder.Entity("Korga.EmailDelivery.Entities.SentEmail", b => + { + b.Property("Id") + .HasColumnType("bigint"); + + b.Property("ContentSize") + .HasColumnType("int"); + + b.Property("DeliveryTime") + .HasColumnType("datetime(6)"); + + b.Property("EmailAddress") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ErrorMessage") + .HasColumnType("longtext"); + + b.Property("InboxEmailId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("DeliveryTime"); + + b.HasIndex("InboxEmailId"); + + b.ToTable("SentEmails"); + }); + + modelBuilder.Entity("Korga.EmailRelay.Entities.DistributionList", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("Alias") + .IsRequired() + .HasColumnType("varchar(255)"); + + b.Property("Flags") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasAlternateKey("Alias"); + + b.ToTable("DistributionLists"); + }); + + modelBuilder.Entity("Korga.EmailRelay.Entities.InboxEmail", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("Body") + .HasColumnType("longblob"); + + b.Property("DistributionListId") + .HasColumnType("bigint"); + + b.Property("DownloadTime") + .ValueGeneratedOnAdd() + .HasColumnType("datetime(6)") + .HasDefaultValueSql("CURRENT_TIMESTAMP(6)"); + + b.Property("From") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Header") + .HasColumnType("longblob"); + + b.Property("ProcessingCompletedTime") + .HasColumnType("datetime(6)"); + + b.Property("Receiver") + .HasColumnType("longtext"); + + b.Property("ReplyTo") + .HasColumnType("longtext"); + + b.Property("Sender") + .HasColumnType("longtext"); + + b.Property("Subject") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("To") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("UniqueId") + .HasColumnType("int unsigned"); + + b.HasKey("Id"); + + b.HasIndex("DistributionListId"); + + b.HasIndex("ProcessingCompletedTime"); + + b.HasIndex("UniqueId") + .IsUnique(); + + b.ToTable("InboxEmails"); + }); + + modelBuilder.Entity("Korga.EmailRelay.Entities.PersonFilter", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("Discriminator") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("DistributionListId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("DistributionListId"); + + b.ToTable("PersonFilters"); + + b.HasDiscriminator("Discriminator").HasValue("PersonFilter"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("Korga.EmailRelay.Entities.GroupFilter", b => + { + b.HasBaseType("Korga.EmailRelay.Entities.PersonFilter"); + + b.Property("GroupId") + .HasColumnType("int"); + + b.Property("GroupRoleId") + .HasColumnType("int"); + + b.HasIndex("GroupId"); + + b.HasIndex("GroupRoleId"); + + b.HasDiscriminator().HasValue("GroupFilter"); + }); + + modelBuilder.Entity("Korga.EmailRelay.Entities.SinglePerson", b => + { + b.HasBaseType("Korga.EmailRelay.Entities.PersonFilter"); + + b.Property("PersonId") + .HasColumnType("int"); + + b.HasIndex("PersonId"); + + b.HasDiscriminator().HasValue("SinglePerson"); + }); + + modelBuilder.Entity("Korga.EmailRelay.Entities.StatusFilter", b => + { + b.HasBaseType("Korga.EmailRelay.Entities.PersonFilter"); + + b.Property("StatusId") + .HasColumnType("int"); + + b.HasIndex("StatusId"); + + b.HasDiscriminator().HasValue("StatusFilter"); + }); + + modelBuilder.Entity("Korga.ChurchTools.Entities.DepartmentMember", b => + { + b.HasOne("Korga.ChurchTools.Entities.Department", "Department") + .WithMany() + .HasForeignKey("DepartmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Korga.ChurchTools.Entities.Person", "Person") + .WithMany("Departments") + .HasForeignKey("PersonId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Department"); + + b.Navigation("Person"); + }); + + modelBuilder.Entity("Korga.ChurchTools.Entities.Group", b => + { + b.HasOne("Korga.ChurchTools.Entities.GroupStatus", "GroupStatus") + .WithMany() + .HasForeignKey("GroupStatusId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Korga.ChurchTools.Entities.GroupType", "GroupType") + .WithMany() + .HasForeignKey("GroupTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("GroupStatus"); + + b.Navigation("GroupType"); + }); + + modelBuilder.Entity("Korga.ChurchTools.Entities.GroupMember", b => + { + b.HasOne("Korga.ChurchTools.Entities.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Korga.ChurchTools.Entities.GroupRole", "GroupRole") + .WithMany() + .HasForeignKey("GroupRoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Korga.ChurchTools.Entities.Person", "Person") + .WithMany() + .HasForeignKey("PersonId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Group"); + + b.Navigation("GroupRole"); + + b.Navigation("Person"); + }); + + modelBuilder.Entity("Korga.ChurchTools.Entities.GroupRole", b => + { + b.HasOne("Korga.ChurchTools.Entities.GroupType", "GroupType") + .WithMany() + .HasForeignKey("GroupTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("GroupType"); + }); + + modelBuilder.Entity("Korga.ChurchTools.Entities.Person", b => + { + b.HasOne("Korga.ChurchTools.Entities.Status", "Status") + .WithMany() + .HasForeignKey("StatusId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Status"); + }); + + modelBuilder.Entity("Korga.EmailDelivery.Entities.OutboxEmail", b => + { + b.HasOne("Korga.EmailRelay.Entities.InboxEmail", "InboxEmail") + .WithMany() + .HasForeignKey("InboxEmailId"); + + b.Navigation("InboxEmail"); + }); + + modelBuilder.Entity("Korga.EmailDelivery.Entities.SentEmail", b => + { + b.HasOne("Korga.EmailRelay.Entities.InboxEmail", "InboxEmail") + .WithMany() + .HasForeignKey("InboxEmailId"); + + b.Navigation("InboxEmail"); + }); + + modelBuilder.Entity("Korga.EmailRelay.Entities.InboxEmail", b => + { + b.HasOne("Korga.EmailRelay.Entities.DistributionList", "DistributionList") + .WithMany() + .HasForeignKey("DistributionListId"); + + b.Navigation("DistributionList"); + }); + + modelBuilder.Entity("Korga.EmailRelay.Entities.PersonFilter", b => + { + b.HasOne("Korga.EmailRelay.Entities.DistributionList", "DistributionList") + .WithMany("Filters") + .HasForeignKey("DistributionListId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DistributionList"); + }); + + modelBuilder.Entity("Korga.EmailRelay.Entities.GroupFilter", b => + { + b.HasOne("Korga.ChurchTools.Entities.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Korga.ChurchTools.Entities.GroupRole", "GroupRole") + .WithMany() + .HasForeignKey("GroupRoleId"); + + b.Navigation("Group"); + + b.Navigation("GroupRole"); + }); + + modelBuilder.Entity("Korga.EmailRelay.Entities.SinglePerson", b => + { + b.HasOne("Korga.ChurchTools.Entities.Person", "Person") + .WithMany() + .HasForeignKey("PersonId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Person"); + }); + + modelBuilder.Entity("Korga.EmailRelay.Entities.StatusFilter", b => + { + b.HasOne("Korga.ChurchTools.Entities.Status", "Status") + .WithMany() + .HasForeignKey("StatusId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Status"); + }); + + modelBuilder.Entity("Korga.ChurchTools.Entities.Person", b => + { + b.Navigation("Departments"); + }); + + modelBuilder.Entity("Korga.EmailRelay.Entities.DistributionList", b => + { + b.Navigation("Filters"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/server/src/Korga.Server/Migrations/20231212132844_RemoveEventRegistration.cs b/server/src/Korga.Server/Migrations/20231212132844_RemoveEventRegistration.cs new file mode 100644 index 0000000..6bebfa1 --- /dev/null +++ b/server/src/Korga.Server/Migrations/20231212132844_RemoveEventRegistration.cs @@ -0,0 +1,100 @@ +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Korga.Server.Migrations +{ + /// + public partial class RemoveEventRegistration : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "EventParticipants"); + + migrationBuilder.DropTable( + name: "EventPrograms"); + + migrationBuilder.DropTable( + name: "Events"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Events", + columns: table => new + { + Id = table.Column(type: "bigint", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + Name = table.Column(type: "longtext", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4") + }, + constraints: table => + { + table.PrimaryKey("PK_Events", x => x.Id); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "EventPrograms", + columns: table => new + { + Id = table.Column(type: "bigint", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + EventId = table.Column(type: "bigint", nullable: false), + Limit = table.Column(type: "int", nullable: false), + Name = table.Column(type: "longtext", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4") + }, + constraints: table => + { + table.PrimaryKey("PK_EventPrograms", x => x.Id); + table.ForeignKey( + name: "FK_EventPrograms_Events_EventId", + column: x => x.EventId, + principalTable: "Events", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "EventParticipants", + columns: table => new + { + Id = table.Column(type: "bigint", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + ProgramId = table.Column(type: "bigint", nullable: false), + FamilyName = table.Column(type: "longtext", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + GivenName = table.Column(type: "longtext", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4") + }, + constraints: table => + { + table.PrimaryKey("PK_EventParticipants", x => x.Id); + table.ForeignKey( + name: "FK_EventParticipants_EventPrograms_ProgramId", + column: x => x.ProgramId, + principalTable: "EventPrograms", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateIndex( + name: "IX_EventParticipants_ProgramId", + table: "EventParticipants", + column: "ProgramId"); + + migrationBuilder.CreateIndex( + name: "IX_EventPrograms_EventId", + table: "EventPrograms", + column: "EventId"); + } + } +} diff --git a/server/src/Korga.Server/Migrations/DatabaseContextModelSnapshot.cs b/server/src/Korga.Server/Migrations/DatabaseContextModelSnapshot.cs index b91dd03..1565e0a 100644 --- a/server/src/Korga.Server/Migrations/DatabaseContextModelSnapshot.cs +++ b/server/src/Korga.Server/Migrations/DatabaseContextModelSnapshot.cs @@ -358,68 +358,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.UseTphMappingStrategy(); }); - modelBuilder.Entity("Korga.EventRegistration.Entities.Event", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - b.Property("Name") - .IsRequired() - .HasColumnType("longtext"); - - b.HasKey("Id"); - - b.ToTable("Events"); - }); - - modelBuilder.Entity("Korga.EventRegistration.Entities.EventParticipant", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - b.Property("FamilyName") - .IsRequired() - .HasColumnType("longtext"); - - b.Property("GivenName") - .IsRequired() - .HasColumnType("longtext"); - - b.Property("ProgramId") - .HasColumnType("bigint"); - - b.HasKey("Id"); - - b.HasIndex("ProgramId"); - - b.ToTable("EventParticipants"); - }); - - modelBuilder.Entity("Korga.EventRegistration.Entities.EventProgram", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - b.Property("EventId") - .HasColumnType("bigint"); - - b.Property("Limit") - .HasColumnType("int"); - - b.Property("Name") - .IsRequired() - .HasColumnType("longtext"); - - b.HasKey("Id"); - - b.HasIndex("EventId"); - - b.ToTable("EventPrograms"); - }); - modelBuilder.Entity("Korga.EmailRelay.Entities.GroupFilter", b => { b.HasBaseType("Korga.EmailRelay.Entities.PersonFilter"); @@ -586,28 +524,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("DistributionList"); }); - modelBuilder.Entity("Korga.EventRegistration.Entities.EventParticipant", b => - { - b.HasOne("Korga.EventRegistration.Entities.EventProgram", "Program") - .WithMany("Participants") - .HasForeignKey("ProgramId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Program"); - }); - - modelBuilder.Entity("Korga.EventRegistration.Entities.EventProgram", b => - { - b.HasOne("Korga.EventRegistration.Entities.Event", "Event") - .WithMany() - .HasForeignKey("EventId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Event"); - }); - modelBuilder.Entity("Korga.EmailRelay.Entities.GroupFilter", b => { b.HasOne("Korga.ChurchTools.Entities.Group", "Group") @@ -656,11 +572,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) { b.Navigation("Filters"); }); - - modelBuilder.Entity("Korga.EventRegistration.Entities.EventProgram", b => - { - b.Navigation("Participants"); - }); #pragma warning restore 612, 618 } } diff --git a/server/src/Korga.Server/Models/Json/EventRegistrationRequest.cs b/server/src/Korga.Server/Models/Json/EventRegistrationRequest.cs deleted file mode 100644 index 2bcb988..0000000 --- a/server/src/Korga.Server/Models/Json/EventRegistrationRequest.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Korga.Server.Models.Json; - -public class EventRegistrationRequest -{ - [JsonConstructor] - public EventRegistrationRequest(string givenName, string familyName) - { - GivenName = givenName; - FamilyName = familyName; - } - - public long ProgramId { get; set; } - public string GivenName { get; set; } - public string FamilyName { get; set; } -} diff --git a/server/src/Korga.Server/Models/Json/EventResponse.cs b/server/src/Korga.Server/Models/Json/EventResponse.cs deleted file mode 100644 index 98a70d9..0000000 --- a/server/src/Korga.Server/Models/Json/EventResponse.cs +++ /dev/null @@ -1,48 +0,0 @@ -using Korga.EventRegistration.Entities; -using System.Collections.Generic; -using System.Text.Json.Serialization; - -namespace Korga.Server.Models.Json; - -public class EventResponse -{ - [JsonConstructor] - public EventResponse(string name, IList programs) - { - Name = name; - Programs = programs; - } - - public EventResponse(Event @event, IList programs) - { - Id = @event.Id; - Name = @event.Name; - Programs = programs; - } - - public long Id { get; set; } - public string Name { get; set; } - public IList Programs { get; set; } - - public class Program - { - [JsonConstructor] - public Program(string name) - { - Name = name; - } - - public Program(EventProgram program, int count) - { - Id = program.Id; - Name = program.Name; - Limit = program.Limit; - Count = count; - } - - public long Id { set; get; } - public string Name { set; get; } - public int Limit { set; get; } - public int Count { set; get; } - } -} diff --git a/server/src/Korga.Server/Models/Json/EventResponse2.cs b/server/src/Korga.Server/Models/Json/EventResponse2.cs deleted file mode 100644 index 989de51..0000000 --- a/server/src/Korga.Server/Models/Json/EventResponse2.cs +++ /dev/null @@ -1,70 +0,0 @@ -using Korga.EventRegistration.Entities; -using System.Collections.Generic; -using System.Text.Json.Serialization; - -namespace Korga.Server.Models.Json; - -public class EventResponse2 -{ - [JsonConstructor] - public EventResponse2(string name, IList programs) - { - Name = name; - Programs = programs; - } - - public EventResponse2(Event @event, IList programs) - { - Id = @event.Id; - Name = @event.Name; - Programs = programs; - } - - public long Id { get; set; } - public string Name { get; set; } - public IList Programs { get; set; } - - public class Program - { - [JsonConstructor] - public Program(string name, IList participants) - { - Name = name; - Participants = participants; - } - - public Program(EventProgram program, IList participants) - { - Id = program.Id; - Name = program.Name; - Limit = program.Limit; - Participants = participants; - } - - public long Id { set; get; } - public string Name { set; get; } - public int Limit { set; get; } - public IList Participants { set; get; } - } - - public class Participant - { - [JsonConstructor] - public Participant(string givenName, string familyName) - { - GivenName = givenName; - FamilyName = familyName; - } - - public Participant(EventParticipant participant) - { - Id = participant.Id; - GivenName = participant.GivenName; - FamilyName = participant.FamilyName; - } - - public long Id { set; get; } - public string GivenName { set; get; } - public string FamilyName { set; get; } - } -} diff --git a/server/tests/Korga.Server.Tests/Http/EventControllerTests.cs b/server/tests/Korga.Server.Tests/Http/EventControllerTests.cs deleted file mode 100644 index e65f0c2..0000000 --- a/server/tests/Korga.Server.Tests/Http/EventControllerTests.cs +++ /dev/null @@ -1,114 +0,0 @@ -using Korga.EventRegistration.Entities; -using Korga.Server.Models.Json; -using Microsoft.AspNetCore.TestHost; -using Microsoft.Extensions.DependencyInjection; -using System; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Net.Http.Json; -using System.Threading.Tasks; -using Xunit; - -namespace Korga.Server.Tests.Http; - -public class EventControllerTests : IDisposable -{ - private readonly TestServer server; - private readonly HttpClient client; - private readonly IServiceScope serviceScope; - private readonly DatabaseContext database; - - private readonly Event testEvent; - private readonly EventProgram testProgram; - - public EventControllerTests() - { - server = TestHost.CreateTestServer(); - client = server.CreateClient(); - serviceScope = server.Services.CreateScope(); - database = serviceScope.ServiceProvider.GetRequiredService(); - - testEvent = new Event(Guid.NewGuid().ToString()); - database.Events.Add(testEvent); - database.SaveChanges(); - - testProgram = new EventProgram(Guid.NewGuid().ToString()) - { - EventId = testEvent.Id, - Limit = 2 - }; - database.EventPrograms.Add(testProgram); - database.SaveChanges(); - } - - public void Dispose() - { - database.Events.Remove(testEvent); - database.SaveChanges(); - serviceScope.Dispose(); - server.Dispose(); - client.Dispose(); - } - - [Fact] - public async Task TestGetEvents_Empty() - { - var events = await client.GetFromJsonAsync("/api/events"); - Assert.NotNull(events); - var response = events.Single(e => e.Id == testEvent.Id); - Assert.Equal(testEvent.Name, response.Name); - var program = response.Programs.Single(); - Assert.Equal(testProgram.Id, program.Id); - Assert.Equal(testProgram.Name, program.Name); - Assert.Equal(testProgram.Limit, program.Limit); - Assert.Equal(0, program.Count); - } - - [Fact] - public async Task TestGetEvent_Full() - { - var participants = new EventParticipant[] - { - new("Katharina", "Schäfer") { ProgramId = testProgram.Id }, - new("Anna", "Schäfer") { ProgramId = testProgram.Id } - }; - database.EventParticipants.AddRange(participants); - await database.SaveChangesAsync(); - - var response = await client.GetFromJsonAsync($"/api/event/{testEvent.Id}"); - Assert.NotNull(response); - Assert.Equal(testEvent.Name, response.Name); - var program = response.Programs.Single(); - Assert.Equal(testProgram.Id, program.Id); - Assert.Equal(testProgram.Name, program.Name); - Assert.Equal(testProgram.Limit, program.Limit); - Assert.Equal(2, program.Participants.Count); - Assert.NotEqual(0, program.Participants.Single(p => p.GivenName == "Katharina" && p.FamilyName == "Schäfer").Id); - Assert.NotEqual(0, program.Participants.Single(p => p.GivenName == "Anna" && p.FamilyName == "Schäfer").Id); - } - - [Fact] - public async Task TestRegister_Single() - { - var request = new EventRegistrationRequest("Max", "Mustermann") { ProgramId = testProgram.Id }; - var response = await client.PostAsJsonAsync("/api/events/register", request); - Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); - } - - [Fact] - public async Task TestRegister_TooMany() - { - EventRegistrationRequest request1 = new("Johannes", "Schäfer") { ProgramId = testProgram.Id }; - var response1 = await client.PostAsJsonAsync("/api/events/register", request1); - Assert.Equal(HttpStatusCode.NoContent, response1.StatusCode); - - EventRegistrationRequest request2 = new("Katharina", "Schäfer") { ProgramId = testProgram.Id }; - var response2 = await client.PostAsJsonAsync("/api/events/register", request2); - Assert.Equal(HttpStatusCode.NoContent, response2.StatusCode); - - EventRegistrationRequest request3 = new("Anna", "Schäfer") { ProgramId = testProgram.Id }; - var response3 = await client.PostAsJsonAsync("/api/events/register", request3); - Assert.Equal(HttpStatusCode.Conflict, response3.StatusCode); - } -} diff --git a/webapp/src/App.vue b/webapp/src/App.vue index e03c828..d624c7b 100644 --- a/webapp/src/App.vue +++ b/webapp/src/App.vue @@ -2,7 +2,7 @@