diff --git a/server/src/Korga.Core/ChurchTools/Api/GroupMember.cs b/server/src/Korga.Core/ChurchTools/Api/GroupMember.cs index 5685a5d..d4dfe07 100644 --- a/server/src/Korga.Core/ChurchTools/Api/GroupMember.cs +++ b/server/src/Korga.Core/ChurchTools/Api/GroupMember.cs @@ -2,16 +2,18 @@ public class GroupMember : IIdentifiable { - public GroupMember(int personId, int groupId, int groupTypeRoleId) + public GroupMember(int personId, int groupId, int groupTypeRoleId, string groupMemberStatus) { PersonId = personId; GroupId = groupId; GroupTypeRoleId = groupTypeRoleId; + GroupMemberStatus = groupMemberStatus; } public int PersonId { get; set; } public int GroupId { get; set; } public int GroupTypeRoleId { get; set; } + public string GroupMemberStatus { get; set; } long IIdentifiable.Id => (((long)PersonId) << 32) | (long)GroupId; } diff --git a/server/src/Korga.Core/ChurchTools/Entities/GroupMember.cs b/server/src/Korga.Core/ChurchTools/Entities/GroupMember.cs index 175791e..e48b863 100644 --- a/server/src/Korga.Core/ChurchTools/Entities/GroupMember.cs +++ b/server/src/Korga.Core/ChurchTools/Entities/GroupMember.cs @@ -2,7 +2,7 @@ public class GroupMember : IIdentifiable { - public int PersonId { get; set; } + public int PersonId { get; set; } public Person? Person { get; set; } public int GroupId { get; set; } @@ -11,5 +11,7 @@ public class GroupMember : IIdentifiable public int GroupRoleId { get; set; } public GroupRole? GroupRole { get; set; } + public GroupMemberStatus GroupMemberStatus { get; set; } + long IIdentifiable.Id => (((long)PersonId) << 32) | (long)GroupId; } diff --git a/server/src/Korga.Core/ChurchTools/GroupMemberStatus.cs b/server/src/Korga.Core/ChurchTools/GroupMemberStatus.cs new file mode 100644 index 0000000..1ac21eb --- /dev/null +++ b/server/src/Korga.Core/ChurchTools/GroupMemberStatus.cs @@ -0,0 +1,8 @@ +namespace Korga.ChurchTools; + +public enum GroupMemberStatus +{ + Active, + Requested, + ToDelete +} diff --git a/server/src/Korga.Core/DatabaseContext.cs b/server/src/Korga.Core/DatabaseContext.cs index dd5a49b..156e8cb 100644 --- a/server/src/Korga.Core/DatabaseContext.cs +++ b/server/src/Korga.Core/DatabaseContext.cs @@ -63,6 +63,7 @@ private void CreateChurchTools(ModelBuilder modelBuilder) groupMember.HasOne(gm => gm.Person).WithMany().HasForeignKey(gm => gm.PersonId); groupMember.HasOne(gm => gm.Group).WithMany().HasForeignKey(gm => gm.GroupId); groupMember.HasOne(gm => gm.GroupRole).WithMany().HasForeignKey(gm => gm.GroupRoleId); + groupMember.Property(gm => gm.GroupMemberStatus).HasConversion(); var groupType = modelBuilder.Entity(); groupType.HasKey(x => x.Id); diff --git a/server/src/Korga.Server/ChurchTools/ChurchToolsSyncService.cs b/server/src/Korga.Server/ChurchTools/ChurchToolsSyncService.cs index af79eb3..c484e3f 100644 --- a/server/src/Korga.Server/ChurchTools/ChurchToolsSyncService.cs +++ b/server/src/Korga.Server/ChurchTools/ChurchToolsSyncService.cs @@ -102,8 +102,18 @@ await Synchronize( await churchTools.GetGroupMembers(cancellationToken), database.GroupMembers, await database.GroupMembers.OrderBy(x => x.PersonId).ThenBy(x => x.GroupId).ToListAsync(cancellationToken), - x => new() { PersonId = x.PersonId, GroupId = x.GroupId, GroupRoleId = x.GroupTypeRoleId }, - (response, entity) => entity.GroupRoleId = response.GroupTypeRoleId, + x => new() + { + PersonId = x.PersonId, + GroupId = x.GroupId, + GroupRoleId = x.GroupTypeRoleId, + GroupMemberStatus = ParseGroupMemberStatus(x.GroupMemberStatus), + }, + (response, entity) => + { + entity.GroupRoleId = response.GroupTypeRoleId; + entity.GroupMemberStatus = ParseGroupMemberStatus(response.GroupMemberStatus); + }, cancellationToken ); } @@ -170,4 +180,15 @@ private void LogChanges(int rowsAffected, DbSet table) where T : class else logger.LogDebug("No changes for {EntityDisplayName} entities", table.EntityType.DisplayName()); } + + private static GroupMemberStatus ParseGroupMemberStatus(string groupMemberStatus) + { + return groupMemberStatus switch + { + "active" => GroupMemberStatus.Active, + "requested" => GroupMemberStatus.Requested, + "to_delete" => GroupMemberStatus.ToDelete, + _ => throw new ArgumentException($"Unknown GroupMemberStatus {groupMemberStatus}") + }; + } } diff --git a/server/src/Korga.Server/Controllers/ServiceController.cs b/server/src/Korga.Server/Controllers/ServiceController.cs index 5a0addb..dbad81f 100644 --- a/server/src/Korga.Server/Controllers/ServiceController.cs +++ b/server/src/Korga.Server/Controllers/ServiceController.cs @@ -62,7 +62,8 @@ join person in database.People on member.PersonId equals person.Id { PersonId = person.Id, FirstName = person.FirstName, - LastName = person.LastName + LastName = person.LastName, + GroupMemberStatus = member.GroupMemberStatus, }) .Distinct() .ToDictionaryAsync(x => x.PersonId); diff --git a/server/src/Korga.Server/Migrations/20240115151648_GroupMemberStatus.Designer.cs b/server/src/Korga.Server/Migrations/20240115151648_GroupMemberStatus.Designer.cs new file mode 100644 index 0000000..663558c --- /dev/null +++ b/server/src/Korga.Server/Migrations/20240115151648_GroupMemberStatus.Designer.cs @@ -0,0 +1,584 @@ +// +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("20240115151648_GroupMemberStatus")] + partial class GroupMemberStatus + { + /// + 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("GroupMemberStatus") + .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/20240115151648_GroupMemberStatus.cs b/server/src/Korga.Server/Migrations/20240115151648_GroupMemberStatus.cs new file mode 100644 index 0000000..39878fa --- /dev/null +++ b/server/src/Korga.Server/Migrations/20240115151648_GroupMemberStatus.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Korga.Server.Migrations +{ + /// + public partial class GroupMemberStatus : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "GroupMemberStatus", + table: "GroupMembers", + type: "int", + nullable: false, + defaultValue: 0); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "GroupMemberStatus", + table: "GroupMembers"); + } + } +} diff --git a/server/src/Korga.Server/Migrations/DatabaseContextModelSnapshot.cs b/server/src/Korga.Server/Migrations/DatabaseContextModelSnapshot.cs index 1565e0a..42bc589 100644 --- a/server/src/Korga.Server/Migrations/DatabaseContextModelSnapshot.cs +++ b/server/src/Korga.Server/Migrations/DatabaseContextModelSnapshot.cs @@ -86,6 +86,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("GroupId") .HasColumnType("int"); + b.Property("GroupMemberStatus") + .HasColumnType("int"); + b.Property("GroupRoleId") .HasColumnType("int"); diff --git a/server/src/Korga.Server/Models/Json/ServiceHistoryResponse.cs b/server/src/Korga.Server/Models/Json/ServiceHistoryResponse.cs index f71953c..78096d8 100644 --- a/server/src/Korga.Server/Models/Json/ServiceHistoryResponse.cs +++ b/server/src/Korga.Server/Models/Json/ServiceHistoryResponse.cs @@ -1,5 +1,7 @@ -using System; +using Korga.ChurchTools; +using System; using System.Collections.Generic; +using System.Text.Json.Serialization; namespace Korga.Server.Models.Json; @@ -8,5 +10,9 @@ public class ServiceHistoryResponse public required int PersonId { get; init; } public required string FirstName { get; init; } public required string LastName { get; init; } + + [JsonConverter(typeof(JsonStringEnumConverter))] + public required GroupMemberStatus GroupMemberStatus { get; init; } + public List ServiceDates { get; } = new(); } diff --git a/webapp/src/services/service.ts b/webapp/src/services/service.ts index c20f24e..bcb59f5 100644 --- a/webapp/src/services/service.ts +++ b/webapp/src/services/service.ts @@ -10,6 +10,7 @@ export interface ServiceHistory { personId: number; firstName: string; lastName: string; + groupMemberStatus: "Active" | "Requested" | "ToDelete"; serviceDates: string[]; } diff --git a/webapp/src/views/ServiceList.vue b/webapp/src/views/ServiceList.vue index fa26a43..543f176 100644 --- a/webapp/src/views/ServiceList.vue +++ b/webapp/src/views/ServiceList.vue @@ -27,7 +27,14 @@ - + {{ index }} {{ person.firstName }} {{ person.lastName }} @@ -59,4 +66,13 @@ const fetchServiceHistory = async function (id: number) { }; - +