From 37443228074acb275850b38f61b247f6c3b81a5f Mon Sep 17 00:00:00 2001 From: Peter Drier Date: Wed, 8 Apr 2026 16:25:24 +0200 Subject: [PATCH 1/2] Add option to promote subteams to Teams directory (#432) - Add IsPromotedToDirectory property to Team entity (default false) - Add IsInDirectory computed property encapsulating visibility logic - Filter /Teams directory to show only top-level teams and promoted subteams - Add "Show on Teams page" checkbox in team edit (visible for subteams only) - Include EF migration for new column - Update feature docs and section invariants Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/features/06-teams.md | 3 + docs/sections/Teams.md | 1 + .../Interfaces/ITeamService.cs | 3 +- src/Humans.Domain/Entities/Team.cs | 13 + .../Data/Configurations/TeamConfiguration.cs | 22 +- ...31712_AddIsPromotedToDirectory.Designer.cs | 4627 +++++++++++++++++ ...20260414231712_AddIsPromotedToDirectory.cs | 72 + .../HumansDbContextModelSnapshot.cs | 9 + .../Services/TeamService.cs | 14 +- src/Humans.Web/Controllers/TeamController.cs | 3 +- src/Humans.Web/Models/TeamViewModels.cs | 1 + src/Humans.Web/Views/Team/EditTeam.cshtml | 9 + 12 files changed, 4766 insertions(+), 11 deletions(-) create mode 100644 src/Humans.Infrastructure/Migrations/20260414231712_AddIsPromotedToDirectory.Designer.cs create mode 100644 src/Humans.Infrastructure/Migrations/20260414231712_AddIsPromotedToDirectory.cs diff --git a/docs/features/06-teams.md b/docs/features/06-teams.md index 065bc86c6..d2f595cb5 100644 --- a/docs/features/06-teams.md +++ b/docs/features/06-teams.md @@ -136,6 +136,7 @@ Nobodies Collective operates through self-organizing working groups (teams). Tea - Shows team name, description snippet, and "Learn More" link - No My Teams section, no admin buttons, no system teams shown - Authenticated users see the full existing layout unchanged +- Subteams only appear if `IsPromotedToDirectory` is true; top-level teams always appear ## Data Model @@ -158,7 +159,9 @@ Team ├── PageContentUpdatedAt: Instant? [last edit timestamp] ├── PageContentUpdatedByUserId: Guid? [FK → User, who last edited] ├── CallsToAction: List [JSONB, max 3 items] +├── IsPromotedToDirectory: bool [default false, promotes subteam to directory] ├── Computed: IsSystemTeam (SystemTeamType != None) +├── Computed: IsInDirectory (ParentTeamId == null || IsPromotedToDirectory) ├── Computed: GoogleGroupEmail (prefix + "@nobodies.team", or null) └── Navigation: Members, JoinRequests, GoogleResources, ChildTeams, ParentTeam ``` diff --git a/docs/sections/Teams.md b/docs/sections/Teams.md index 330b704a1..9695b221f 100644 --- a/docs/sections/Teams.md +++ b/docs/sections/Teams.md @@ -38,6 +38,7 @@ - A Google Group prefix, if set, provisions a @nobodies.team group for the team. - Only departments (not sub-teams or system teams) can have public team pages. - A **hidden team** (`IsHidden = true`) is invisible to non-admin users: it does not appear on profile cards, team listings, public pages, birthday team names, or the "My Teams" page. Only Admin, Board, and TeamsAdmin can see and manage hidden teams. Campaigns can still target hidden teams for code distribution. +- The Teams directory (`/Teams`) shows only **directory-visible** teams: top-level teams (departments) always appear; sub-teams only appear if `IsPromotedToDirectory` is true. Sub-teams are always accessible from their parent team's detail page regardless of this flag. ## Negative Access Rules diff --git a/src/Humans.Application/Interfaces/ITeamService.cs b/src/Humans.Application/Interfaces/ITeamService.cs index b0ccbacbc..91743abb9 100644 --- a/src/Humans.Application/Interfaces/ITeamService.cs +++ b/src/Humans.Application/Interfaces/ITeamService.cs @@ -8,7 +8,7 @@ namespace Humans.Application.Interfaces; public record CachedTeam( Guid Id, string Name, string? Description, string Slug, bool IsSystemTeam, SystemTeamType SystemTeamType, bool RequiresApproval, - bool IsPublicPage, bool IsHidden, Instant CreatedAt, List Members, + bool IsPublicPage, bool IsHidden, bool IsPromotedToDirectory, Instant CreatedAt, List Members, Guid? ParentTeamId = null); public record CachedTeamMember( @@ -186,6 +186,7 @@ Task UpdateTeamAsync( bool? hasBudget = null, bool? isHidden = null, bool? isSensitive = null, + bool? isPromotedToDirectory = null, CancellationToken cancellationToken = default); /// diff --git a/src/Humans.Domain/Entities/Team.cs b/src/Humans.Domain/Entities/Team.cs index 0b43183dd..87fd2c023 100644 --- a/src/Humans.Domain/Entities/Team.cs +++ b/src/Humans.Domain/Entities/Team.cs @@ -164,6 +164,19 @@ public class Team /// public ICollection RoleDefinitions { get; } = new List(); + /// + /// Whether this subteam is promoted to appear on the Teams directory page. + /// Only meaningful for subteams (ParentTeamId != null). Top-level teams always appear. + /// + public bool IsPromotedToDirectory { get; set; } + + /// + /// Whether this team should appear in the Teams directory. + /// Top-level teams always appear; subteams only if promoted. + /// Not mapped to DB — use inline expression for EF queries. + /// + public bool IsInDirectory => ParentTeamId == null || IsPromotedToDirectory; + /// /// Whether this is a system-managed team. /// diff --git a/src/Humans.Infrastructure/Data/Configurations/TeamConfiguration.cs b/src/Humans.Infrastructure/Data/Configurations/TeamConfiguration.cs index b400e5fc9..3e4937248 100644 --- a/src/Humans.Infrastructure/Data/Configurations/TeamConfiguration.cs +++ b/src/Humans.Infrastructure/Data/Configurations/TeamConfiguration.cs @@ -76,6 +76,9 @@ public void Configure(EntityTypeBuilder builder) builder.Property(t => t.IsSensitive) .IsRequired(); + builder.Property(t => t.IsPromotedToDirectory) + .IsRequired(); + builder.Property(t => t.PageContent) .HasMaxLength(50000); @@ -135,6 +138,7 @@ public void Configure(EntityTypeBuilder builder) // Ignore computed properties builder.Ignore(t => t.IsSystemTeam); + builder.Ignore(t => t.IsInDirectory); builder.Ignore(t => t.GoogleGroupEmail); builder.Ignore(t => t.DisplayName); @@ -162,7 +166,8 @@ public void Configure(EntityTypeBuilder builder) ShowCoordinatorsOnPublicPage = true, HasBudget = false, IsHidden = false, - IsSensitive = false + IsSensitive = false, + IsPromotedToDirectory = false }, new { @@ -186,7 +191,8 @@ public void Configure(EntityTypeBuilder builder) ShowCoordinatorsOnPublicPage = true, HasBudget = false, IsHidden = false, - IsSensitive = false + IsSensitive = false, + IsPromotedToDirectory = false }, new { @@ -210,7 +216,8 @@ public void Configure(EntityTypeBuilder builder) ShowCoordinatorsOnPublicPage = true, HasBudget = false, IsHidden = false, - IsSensitive = false + IsSensitive = false, + IsPromotedToDirectory = false }, new { @@ -234,7 +241,8 @@ public void Configure(EntityTypeBuilder builder) ShowCoordinatorsOnPublicPage = true, HasBudget = false, IsHidden = false, - IsSensitive = false + IsSensitive = false, + IsPromotedToDirectory = false }, new { @@ -258,7 +266,8 @@ public void Configure(EntityTypeBuilder builder) ShowCoordinatorsOnPublicPage = true, HasBudget = false, IsHidden = false, - IsSensitive = false + IsSensitive = false, + IsPromotedToDirectory = false }, new { @@ -282,7 +291,8 @@ public void Configure(EntityTypeBuilder builder) ShowCoordinatorsOnPublicPage = true, HasBudget = false, IsHidden = false, - IsSensitive = false + IsSensitive = false, + IsPromotedToDirectory = false }); } } diff --git a/src/Humans.Infrastructure/Migrations/20260414231712_AddIsPromotedToDirectory.Designer.cs b/src/Humans.Infrastructure/Migrations/20260414231712_AddIsPromotedToDirectory.Designer.cs new file mode 100644 index 000000000..ca1acd138 --- /dev/null +++ b/src/Humans.Infrastructure/Migrations/20260414231712_AddIsPromotedToDirectory.Designer.cs @@ -0,0 +1,4627 @@ +// +using System; +using Humans.Infrastructure.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using NodaTime; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Humans.Infrastructure.Migrations +{ + [DbContext(typeof(HumansDbContext))] + [Migration("20260414231712_AddIsPromotedToDirectory")] + partial class AddIsPromotedToDirectory + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Humans.Domain.Entities.AccountMergeRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminNotes") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PendingEmailId") + .HasColumnType("uuid"); + + b.Property("ResolvedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ResolvedByUserId") + .HasColumnType("uuid"); + + b.Property("SourceUserId") + .HasColumnType("uuid"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("TargetUserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ResolvedByUserId"); + + b.HasIndex("SourceUserId"); + + b.HasIndex("Status"); + + b.HasIndex("TargetUserId"); + + b.ToTable("account_merge_requests", (string)null); + }); + + modelBuilder.Entity("Humans.Domain.Entities.Application", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdditionalInfo") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("BoardMeetingDate") + .HasColumnType("date"); + + b.Property("DecisionNote") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("Language") + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("MembershipTier") + .IsRequired() + .HasColumnType("text"); + + b.Property("Motivation") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("RenewalReminderSentAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ResolvedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ReviewNotes") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("ReviewStartedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ReviewedByUserId") + .HasColumnType("uuid"); + + b.Property("RoleUnderstanding") + .HasColumnType("text"); + + b.Property("SignificantContribution") + .HasColumnType("text"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("SubmittedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("TermExpiresAt") + .HasColumnType("date"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("MembershipTier"); + + b.HasIndex("ReviewedByUserId"); + + b.HasIndex("Status"); + + b.HasIndex("SubmittedAt"); + + b.HasIndex("UserId"); + + b.HasIndex("UserId", "Status"); + + b.ToTable("applications", (string)null); + }); + + modelBuilder.Entity("Humans.Domain.Entities.ApplicationStateHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("ChangedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ChangedByUserId") + .HasColumnType("uuid"); + + b.Property("Notes") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.HasIndex("ChangedAt"); + + b.HasIndex("ChangedByUserId"); + + b.ToTable("application_state_history", (string)null); + }); + + modelBuilder.Entity("Humans.Domain.Entities.AuditLogEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ActorUserId") + .HasColumnType("uuid"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("EntityId") + .HasColumnType("uuid"); + + b.Property("EntityType") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ErrorMessage") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("OccurredAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RelatedEntityId") + .HasColumnType("uuid"); + + b.Property("RelatedEntityType") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ResourceId") + .HasColumnType("uuid"); + + b.Property("Role") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Success") + .HasColumnType("boolean"); + + b.Property("SyncSource") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("UserEmail") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.HasKey("Id"); + + b.HasIndex("Action"); + + b.HasIndex("ActorUserId"); + + b.HasIndex("OccurredAt"); + + b.HasIndex("ResourceId"); + + b.HasIndex("EntityType", "EntityId"); + + b.HasIndex("RelatedEntityType", "RelatedEntityId"); + + b.ToTable("audit_log", (string)null); + }); + + modelBuilder.Entity("Humans.Domain.Entities.BoardVote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApplicationId") + .HasColumnType("uuid"); + + b.Property("BoardMemberUserId") + .HasColumnType("uuid"); + + b.Property("Note") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Vote") + .IsRequired() + .HasColumnType("text"); + + b.Property("VotedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId"); + + b.HasIndex("BoardMemberUserId"); + + b.HasIndex("ApplicationId", "BoardMemberUserId") + .IsUnique(); + + b.ToTable("board_votes", (string)null); + }); + + modelBuilder.Entity("Humans.Domain.Entities.BudgetAuditLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ActorUserId") + .HasColumnType("uuid"); + + b.Property("BudgetYearId") + .HasColumnType("uuid"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("EntityId") + .HasColumnType("uuid"); + + b.Property("EntityType") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("FieldName") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("NewValue") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("OccurredAt") + .HasColumnType("timestamp with time zone"); + + b.Property("OldValue") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.HasKey("Id"); + + b.HasIndex("ActorUserId"); + + b.HasIndex("BudgetYearId"); + + b.HasIndex("OccurredAt"); + + b.HasIndex("EntityType", "EntityId"); + + b.ToTable("budget_audit_logs", (string)null); + }); + + modelBuilder.Entity("Humans.Domain.Entities.BudgetCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AllocatedAmount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("BudgetGroupId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ExpenditureType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("SortOrder") + .HasColumnType("integer"); + + b.Property("TeamId") + .HasColumnType("uuid"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("TeamId") + .HasFilter("\"TeamId\" IS NOT NULL"); + + b.HasIndex("BudgetGroupId", "SortOrder"); + + b.ToTable("budget_categories", (string)null); + }); + + modelBuilder.Entity("Humans.Domain.Entities.BudgetGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BudgetYearId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsDepartmentGroup") + .HasColumnType("boolean"); + + b.Property("IsRestricted") + .HasColumnType("boolean"); + + b.Property("IsTicketingGroup") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("SortOrder") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("BudgetYearId", "SortOrder"); + + b.ToTable("budget_groups", (string)null); + }); + + modelBuilder.Entity("Humans.Domain.Entities.BudgetLineItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Amount") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("BudgetCategoryId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("ExpectedDate") + .HasColumnType("date"); + + b.Property("IsAutoGenerated") + .HasColumnType("boolean"); + + b.Property("IsCashflowOnly") + .HasColumnType("boolean"); + + b.Property("Notes") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("ResponsibleTeamId") + .HasColumnType("uuid"); + + b.Property("SortOrder") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("VatRate") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ResponsibleTeamId") + .HasFilter("\"ResponsibleTeamId\" IS NOT NULL"); + + b.HasIndex("BudgetCategoryId", "SortOrder"); + + b.ToTable("budget_line_items", (string)null); + }); + + modelBuilder.Entity("Humans.Domain.Entities.BudgetYear", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Year") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.HasIndex("Status"); + + b.HasIndex("Year") + .IsUnique(); + + b.ToTable("budget_years", (string)null); + }); + + modelBuilder.Entity("Humans.Domain.Entities.Camp", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ContactEmail") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ContactPhone") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedByUserId") + .HasColumnType("uuid"); + + b.Property("HideHistoricalNames") + .HasColumnType("boolean"); + + b.Property("IsSwissCamp") + .HasColumnType("boolean"); + + b.Property("Links") + .HasColumnType("jsonb"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("TimesAtNowhere") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("WebOrSocialUrl") + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedByUserId"); + + b.HasIndex("Slug") + .IsUnique(); + + b.ToTable("camps", (string)null); + }); + + modelBuilder.Entity("Humans.Domain.Entities.CampHistoricalName", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CampId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Source") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Year") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CampId"); + + b.ToTable("camp_historical_names", (string)null); + }); + + modelBuilder.Entity("Humans.Domain.Entities.CampImage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CampId") + .HasColumnType("uuid"); + + b.Property("ContentType") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("FileName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("SortOrder") + .HasColumnType("integer"); + + b.Property("StoragePath") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("UploadedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("CampId"); + + b.ToTable("camp_images", (string)null); + }); + + modelBuilder.Entity("Humans.Domain.Entities.CampLead", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CampId") + .HasColumnType("uuid"); + + b.Property("JoinedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LeftAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("CampId", "UserId") + .IsUnique() + .HasDatabaseName("IX_camp_leads_active_unique") + .HasFilter("\"LeftAt\" IS NULL"); + + b.ToTable("camp_leads", (string)null); + }); + + modelBuilder.Entity("Humans.Domain.Entities.CampPolygon", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AreaSqm") + .HasColumnType("double precision"); + + b.Property("CampSeasonId") + .HasColumnType("uuid"); + + b.Property("GeoJson") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastModifiedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastModifiedByUserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("CampSeasonId") + .IsUnique(); + + b.HasIndex("LastModifiedByUserId"); + + b.ToTable("camp_polygons", (string)null); + }); + + modelBuilder.Entity("Humans.Domain.Entities.CampPolygonHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AreaSqm") + .HasColumnType("double precision"); + + b.Property("CampSeasonId") + .HasColumnType("uuid"); + + b.Property("GeoJson") + .IsRequired() + .HasColumnType("text"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ModifiedByUserId") + .HasColumnType("uuid"); + + b.Property("Note") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.HasKey("Id"); + + b.HasIndex("ModifiedByUserId"); + + b.HasIndex("CampSeasonId", "ModifiedAt"); + + b.ToTable("camp_polygon_histories", (string)null); + }); + + modelBuilder.Entity("Humans.Domain.Entities.CampSeason", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AcceptingMembers") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("AdultPlayspace") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BlurbLong") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("BlurbShort") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("CampId") + .HasColumnType("uuid"); + + b.Property("ContainerCount") + .HasColumnType("integer"); + + b.Property("ContainerNotes") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ElectricalGrid") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("HasPerformanceSpace") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("KidsAreaDescription") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("KidsVisiting") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("KidsWelcome") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Languages") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("MemberCount") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NameLockDate") + .HasColumnType("date"); + + b.Property("NameLockedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("PerformanceTypes") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("ResolvedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ReviewNotes") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("ReviewedByUserId") + .HasColumnType("uuid"); + + b.Property("SoundZone") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("SpaceRequirement") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Vibes") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Year") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ReviewedByUserId"); + + b.HasIndex("Status"); + + b.HasIndex("CampId", "Year") + .IsUnique(); + + b.ToTable("camp_seasons", (string)null); + }); + + modelBuilder.Entity("Humans.Domain.Entities.CampSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("OpenSeasons") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("PublicYear") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("camp_settings", (string)null); + + b.HasData( + new + { + Id = new Guid("00000000-0000-0000-0010-000000000001"), + OpenSeasons = "[2026]", + PublicYear = 2026 + }); + }); + + modelBuilder.Entity("Humans.Domain.Entities.Campaign", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedByUserId") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("EmailBodyTemplate") + .IsRequired() + .HasColumnType("text"); + + b.Property("EmailSubject") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("ReplyToAddress") + .HasMaxLength(320) + .HasColumnType("character varying(320)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedByUserId"); + + b.ToTable("campaigns", (string)null); + }); + + modelBuilder.Entity("Humans.Domain.Entities.CampaignCode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CampaignId") + .HasColumnType("uuid"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ImportOrder") + .HasColumnType("integer"); + + b.Property("ImportedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("CampaignId", "Code") + .IsUnique(); + + b.ToTable("campaign_codes", (string)null); + }); + + modelBuilder.Entity("Humans.Domain.Entities.CampaignGrant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AssignedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CampaignCodeId") + .HasColumnType("uuid"); + + b.Property("CampaignId") + .HasColumnType("uuid"); + + b.Property("LatestEmailAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LatestEmailStatus") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("RedeemedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("CampaignCodeId") + .IsUnique(); + + b.HasIndex("UserId"); + + b.HasIndex("CampaignId", "UserId") + .IsUnique(); + + b.ToTable("campaign_grants", (string)null); + }); + + modelBuilder.Entity("Humans.Domain.Entities.CityPlanningSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClosedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsPlacementOpen") + .HasColumnType("boolean"); + + b.Property("LimitZoneGeoJson") + .HasColumnType("text"); + + b.Property("OfficialZonesGeoJson") + .HasColumnType("text"); + + b.Property("OpenedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("PlacementClosesAt") + .HasColumnType("timestamp without time zone"); + + b.Property("PlacementOpensAt") + .HasColumnType("timestamp without time zone"); + + b.Property("RegistrationInfo") + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Year") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Year") + .IsUnique(); + + b.ToTable("city_planning_settings", (string)null); + }); + + modelBuilder.Entity("Humans.Domain.Entities.CommunicationPreference", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Category") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("InboxEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.Property("OptedOut") + .HasColumnType("boolean"); + + b.Property("UpdateSource") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("UserId", "Category") + .IsUnique(); + + b.ToTable("communication_preferences", (string)null); + }); + + modelBuilder.Entity("Humans.Domain.Entities.ConsentRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConsentedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ContentHash") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("DocumentVersionId") + .HasColumnType("uuid"); + + b.Property("ExplicitConsent") + .HasColumnType("boolean"); + + b.Property("IpAddress") + .IsRequired() + .HasMaxLength(45) + .HasColumnType("character varying(45)"); + + b.Property("UserAgent") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ConsentedAt"); + + b.HasIndex("DocumentVersionId"); + + b.HasIndex("UserId"); + + b.HasIndex("UserId", "DocumentVersionId") + .IsUnique(); + + b.HasIndex("UserId", "ExplicitConsent", "ConsentedAt"); + + b.ToTable("consent_records", (string)null); + }); + + modelBuilder.Entity("Humans.Domain.Entities.ContactField", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CustomLabel") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("DisplayOrder") + .HasColumnType("integer"); + + b.Property("FieldType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ProfileId") + .HasColumnType("uuid"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Visibility") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.HasIndex("ProfileId"); + + b.HasIndex("ProfileId", "Visibility"); + + b.ToTable("contact_fields", (string)null); + }); + + modelBuilder.Entity("Humans.Domain.Entities.DocumentVersion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ChangesSummary") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("CommitSha") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)"); + + b.Property("Content") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("jsonb") + .HasDefaultValueSql("'{}'::jsonb"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EffectiveFrom") + .HasColumnType("timestamp with time zone"); + + b.Property("LegalDocumentId") + .HasColumnType("uuid"); + + b.Property("RequiresReConsent") + .HasColumnType("boolean"); + + b.Property("VersionNumber") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.HasIndex("CommitSha"); + + b.HasIndex("EffectiveFrom"); + + b.HasIndex("LegalDocumentId"); + + b.ToTable("document_versions", (string)null); + }); + + modelBuilder.Entity("Humans.Domain.Entities.EmailOutboxMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CampaignGrantId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ExtraHeaders") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("HtmlBody") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastError") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("NextRetryAt") + .HasColumnType("timestamp with time zone"); + + b.Property("PickedUpAt") + .HasColumnType("timestamp with time zone"); + + b.Property("PlainTextBody") + .HasColumnType("text"); + + b.Property("RecipientEmail") + .IsRequired() + .HasMaxLength(320) + .HasColumnType("character varying(320)"); + + b.Property("RecipientName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ReplyTo") + .HasMaxLength(320) + .HasColumnType("character varying(320)"); + + b.Property("RetryCount") + .HasColumnType("integer"); + + b.Property("SentAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ShiftSignupId") + .HasColumnType("uuid"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Subject") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("TemplateName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("CampaignGrantId"); + + b.HasIndex("UserId"); + + b.HasIndex("ShiftSignupId", "TemplateName") + .HasFilter("\"ShiftSignupId\" IS NOT NULL"); + + b.HasIndex("SentAt", "RetryCount", "NextRetryAt", "PickedUpAt"); + + b.ToTable("email_outbox_messages", (string)null); + }); + + modelBuilder.Entity("Humans.Domain.Entities.EventParticipation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DeclaredAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Source") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("Year") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("UserId", "Year") + .IsUnique(); + + b.ToTable("event_participations", (string)null); + }); + + modelBuilder.Entity("Humans.Domain.Entities.EventSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BarriosEarlyEntryAllocation") + .HasColumnType("jsonb"); + + b.Property("BuildStartOffset") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EarlyEntryCapacity") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("EarlyEntryClose") + .HasColumnType("timestamp with time zone"); + + b.Property("EventEndOffset") + .HasColumnType("integer"); + + b.Property("EventName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("GateOpeningDate") + .HasColumnType("date"); + + b.Property("GlobalVolunteerCap") + .HasColumnType("integer"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("IsShiftBrowsingOpen") + .HasColumnType("boolean"); + + b.Property("ReminderLeadTimeHours") + .HasColumnType("integer"); + + b.Property("StrikeEndOffset") + .HasColumnType("integer"); + + b.Property("TimeZoneId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Year") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("IsActive"); + + b.ToTable("event_settings", (string)null); + }); + + modelBuilder.Entity("Humans.Domain.Entities.FeedbackMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Content") + .IsRequired() + .HasMaxLength(5000) + .HasColumnType("character varying(5000)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("FeedbackReportId") + .HasColumnType("uuid"); + + b.Property("SenderUserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("FeedbackReportId"); + + b.HasIndex("SenderUserId"); + + b.ToTable("feedback_messages", (string)null); + }); + + modelBuilder.Entity("Humans.Domain.Entities.FeedbackReport", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdditionalContext") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("AssignedToTeamId") + .HasColumnType("uuid"); + + b.Property("AssignedToUserId") + .HasColumnType("uuid"); + + b.Property("Category") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(5000) + .HasColumnType("character varying(5000)"); + + b.Property("GitHubIssueNumber") + .HasColumnType("integer"); + + b.Property("LastAdminMessageAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastReporterMessageAt") + .HasColumnType("timestamp with time zone"); + + b.Property("PageUrl") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("ResolvedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ResolvedByUserId") + .HasColumnType("uuid"); + + b.Property("ScreenshotContentType") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("ScreenshotFileName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ScreenshotStoragePath") + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserAgent") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AssignedToTeamId"); + + b.HasIndex("AssignedToUserId"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("ResolvedByUserId"); + + b.HasIndex("Status"); + + b.HasIndex("UserId"); + + b.ToTable("feedback_reports", (string)null); + }); + + modelBuilder.Entity("Humans.Domain.Entities.GeneralAvailability", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.PrimitiveCollection("AvailableDayOffsets") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EventSettingsId") + .HasColumnType("uuid"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("EventSettingsId"); + + b.HasIndex("UserId", "EventSettingsId") + .IsUnique(); + + b.ToTable("general_availability", (string)null); + }); + + modelBuilder.Entity("Humans.Domain.Entities.GoogleResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DrivePermissionLevel") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("ErrorMessage") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("GoogleId") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("ProvisionedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ResourceType") + .IsRequired() + .HasColumnType("text"); + + b.Property("RestrictInheritedAccess") + .HasColumnType("boolean"); + + b.Property("TeamId") + .HasColumnType("uuid"); + + b.Property("Url") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.HasKey("Id"); + + b.HasIndex("GoogleId"); + + b.HasIndex("IsActive"); + + b.HasIndex("TeamId"); + + b.HasIndex("TeamId", "GoogleId") + .IsUnique() + .HasFilter("\"IsActive\" = true"); + + b.ToTable("google_resources", (string)null); + }); + + modelBuilder.Entity("Humans.Domain.Entities.GoogleSyncOutboxEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DeduplicationKey") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("EventType") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("FailedPermanently") + .HasColumnType("boolean"); + + b.Property("LastError") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("OccurredAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ProcessedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RetryCount") + .HasColumnType("integer"); + + b.Property("TeamId") + .HasColumnType("uuid"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("DeduplicationKey") + .IsUnique(); + + b.HasIndex("UserId"); + + b.HasIndex("ProcessedAt", "OccurredAt"); + + b.HasIndex("TeamId", "UserId", "ProcessedAt"); + + b.ToTable("google_sync_outbox", (string)null); + }); + + modelBuilder.Entity("Humans.Domain.Entities.LegalDocument", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CurrentCommitSha") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)"); + + b.Property("GitHubFolderPath") + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("GracePeriodDays") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(7); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("IsRequired") + .HasColumnType("boolean"); + + b.Property("LastSyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("TeamId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("IsActive"); + + b.HasIndex("TeamId", "IsActive"); + + b.ToTable("legal_documents", (string)null); + }); + + modelBuilder.Entity("Humans.Domain.Entities.Notification", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ActionLabel") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ActionUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Body") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("Class") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Priority") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ResolvedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ResolvedByUserId") + .HasColumnType("uuid"); + + b.Property("Source") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("TargetGroupName") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("ResolvedByUserId"); + + b.ToTable("notifications", (string)null); + }); + + modelBuilder.Entity("Humans.Domain.Entities.NotificationRecipient", b => + { + b.Property("NotificationId") + .HasColumnType("uuid"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("ReadAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("NotificationId", "UserId"); + + b.HasIndex("UserId") + .HasDatabaseName("IX_NotificationRecipient_UserId"); + + b.ToTable("notification_recipients", (string)null); + }); + + modelBuilder.Entity("Humans.Domain.Entities.Profile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminNotes") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("Bio") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("BoardNotes") + .HasColumnType("text"); + + b.Property("BurnerName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("City") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ConsentCheckAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ConsentCheckNotes") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("ConsentCheckStatus") + .HasColumnType("text"); + + b.Property("ConsentCheckedByUserId") + .HasColumnType("uuid"); + + b.Property("ContributionInterests") + .HasColumnType("text"); + + b.Property("CountryCode") + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DateOfBirth") + .HasColumnType("date"); + + b.Property("EmergencyContactName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmergencyContactPhone") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("EmergencyContactRelationship") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("FirstName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsApproved") + .HasColumnType("boolean"); + + b.Property("IsSuspended") + .HasColumnType("boolean"); + + b.Property("LastName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Latitude") + .HasColumnType("double precision"); + + b.Property("Longitude") + .HasColumnType("double precision"); + + b.Property("MembershipTier") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("text") + .HasDefaultValue("Volunteer"); + + b.Property("NoPriorBurnExperience") + .HasColumnType("boolean"); + + b.Property("PlaceId") + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("ProfilePictureContentType") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ProfilePictureData") + .HasColumnType("bytea"); + + b.Property("Pronouns") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("RejectedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RejectedByUserId") + .HasColumnType("uuid"); + + b.Property("RejectionReason") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ConsentCheckStatus"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("profiles", (string)null); + }); + + modelBuilder.Entity("Humans.Domain.Entities.ProfileLanguage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("LanguageCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("Proficiency") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ProfileId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ProfileId"); + + b.ToTable("profile_languages", (string)null); + }); + + modelBuilder.Entity("Humans.Domain.Entities.RoleAssignment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedByUserId") + .HasColumnType("uuid"); + + b.Property("Notes") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("RoleName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("ValidFrom") + .HasColumnType("timestamp with time zone"); + + b.Property("ValidTo") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("CreatedByUserId"); + + b.HasIndex("RoleName"); + + b.HasIndex("UserId"); + + b.HasIndex("UserId", "RoleName") + .HasFilter("\"ValidTo\" IS NULL"); + + b.HasIndex("UserId", "RoleName", "ValidFrom"); + + b.ToTable("role_assignments", null, t => + { + t.HasCheckConstraint("CK_role_assignments_valid_window", "\"ValidTo\" IS NULL OR \"ValidTo\" > \"ValidFrom\""); + }); + }); + + modelBuilder.Entity("Humans.Domain.Entities.Rota", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("EventSettingsId") + .HasColumnType("uuid"); + + b.Property("IsVisibleToVolunteers") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Period") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Policy") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PracticalInfo") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("Priority") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("TeamId") + .HasColumnType("uuid"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("TeamId"); + + b.HasIndex("EventSettingsId", "TeamId"); + + b.ToTable("rotas", (string)null); + }); + + modelBuilder.Entity("Humans.Domain.Entities.Shift", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AdminOnly") + .HasColumnType("boolean"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DayOffset") + .HasColumnType("integer"); + + b.Property("Description") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("Duration") + .HasColumnType("bigint"); + + b.Property("IsAllDay") + .HasColumnType("boolean"); + + b.Property("MaxVolunteers") + .HasColumnType("integer"); + + b.Property("MinVolunteers") + .HasColumnType("integer"); + + b.Property("RotaId") + .HasColumnType("uuid"); + + b.Property("StartTime") + .HasColumnType("time"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("RotaId"); + + b.ToTable("shifts", (string)null); + }); + + modelBuilder.Entity("Humans.Domain.Entities.ShiftSignup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Enrolled") + .HasColumnType("boolean"); + + b.Property("EnrolledByUserId") + .HasColumnType("uuid"); + + b.Property("ReviewedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ReviewedByUserId") + .HasColumnType("uuid"); + + b.Property("ShiftId") + .HasColumnType("uuid"); + + b.Property("SignupBlockId") + .HasColumnType("uuid"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("StatusReason") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("EnrolledByUserId"); + + b.HasIndex("ReviewedByUserId"); + + b.HasIndex("ShiftId"); + + b.HasIndex("SignupBlockId"); + + b.HasIndex("UserId"); + + b.HasIndex("ShiftId", "Status"); + + b.ToTable("shift_signups", (string)null); + }); + + modelBuilder.Entity("Humans.Domain.Entities.ShiftTag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique() + .HasDatabaseName("IX_shift_tags_name_unique"); + + b.ToTable("shift_tags", (string)null); + + b.HasData( + new + { + Id = new Guid("00000000-0000-0000-0003-000000000001"), + Name = "Heavy lifting" + }, + new + { + Id = new Guid("00000000-0000-0000-0003-000000000002"), + Name = "Working in the sun" + }, + new + { + Id = new Guid("00000000-0000-0000-0003-000000000003"), + Name = "Working in the shade" + }, + new + { + Id = new Guid("00000000-0000-0000-0003-000000000004"), + Name = "Organisational task" + }, + new + { + Id = new Guid("00000000-0000-0000-0003-000000000005"), + Name = "Meeting new people" + }, + new + { + Id = new Guid("00000000-0000-0000-0003-000000000006"), + Name = "Looking after folks" + }, + new + { + Id = new Guid("00000000-0000-0000-0003-000000000007"), + Name = "Exploring the site" + }, + new + { + Id = new Guid("00000000-0000-0000-0003-000000000008"), + Name = "Feeding and hydrating folks" + }); + }); + + modelBuilder.Entity("Humans.Domain.Entities.SyncServiceSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ServiceType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("SyncMode") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedByUserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ServiceType") + .IsUnique(); + + b.HasIndex("UpdatedByUserId"); + + b.ToTable("sync_service_settings", (string)null); + + b.HasData( + new + { + Id = new Guid("00000000-0000-0000-0002-000000000001"), + ServiceType = "GoogleDrive", + SyncMode = "None", + UpdatedAt = NodaTime.Instant.FromUnixTimeTicks(17730144000000000L) + }, + new + { + Id = new Guid("00000000-0000-0000-0002-000000000002"), + ServiceType = "GoogleGroups", + SyncMode = "None", + UpdatedAt = NodaTime.Instant.FromUnixTimeTicks(17730144000000000L) + }, + new + { + Id = new Guid("00000000-0000-0000-0002-000000000003"), + ServiceType = "Discord", + SyncMode = "None", + UpdatedAt = NodaTime.Instant.FromUnixTimeTicks(17730144000000000L) + }); + }); + + modelBuilder.Entity("Humans.Domain.Entities.SystemSetting", b => + { + b.Property("Key") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.HasKey("Key"); + + b.ToTable("system_settings", (string)null); + + b.HasData( + new + { + Key = "IsEmailSendingPaused", + Value = "false" + }); + }); + + modelBuilder.Entity("Humans.Domain.Entities.Team", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CallsToAction") + .HasColumnType("jsonb"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CustomSlug") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Description") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("GoogleGroupPrefix") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("HasBudget") + .HasColumnType("boolean"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("IsHidden") + .HasColumnType("boolean"); + + b.Property("IsPromotedToDirectory") + .HasColumnType("boolean"); + + b.Property("IsPublicPage") + .HasColumnType("boolean"); + + b.Property("IsSensitive") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PageContent") + .HasMaxLength(50000) + .HasColumnType("character varying(50000)"); + + b.Property("PageContentUpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("PageContentUpdatedByUserId") + .HasColumnType("uuid"); + + b.Property("ParentTeamId") + .HasColumnType("uuid"); + + b.Property("RequiresApproval") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.Property("ShowCoordinatorsOnPublicPage") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("SystemTeamType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("CustomSlug") + .IsUnique() + .HasFilter("\"CustomSlug\" IS NOT NULL"); + + b.HasIndex("GoogleGroupPrefix") + .IsUnique() + .HasFilter("\"GoogleGroupPrefix\" IS NOT NULL"); + + b.HasIndex("IsActive"); + + b.HasIndex("ParentTeamId"); + + b.HasIndex("Slug") + .IsUnique(); + + b.HasIndex("SystemTeamType"); + + b.ToTable("teams", (string)null); + + b.HasData( + new + { + Id = new Guid("00000000-0000-0000-0001-000000000001"), + CreatedAt = NodaTime.Instant.FromUnixTimeTicks(17702491570000000L), + Description = "All active volunteers with signed required documents", + HasBudget = false, + IsActive = true, + IsHidden = false, + IsPromotedToDirectory = false, + IsPublicPage = false, + IsSensitive = false, + Name = "Volunteers", + RequiresApproval = false, + ShowCoordinatorsOnPublicPage = true, + Slug = "volunteers", + SystemTeamType = "Volunteers", + UpdatedAt = NodaTime.Instant.FromUnixTimeTicks(17702491570000000L) + }, + new + { + Id = new Guid("00000000-0000-0000-0001-000000000002"), + CreatedAt = NodaTime.Instant.FromUnixTimeTicks(17702491570000000L), + Description = "All team coordinators", + HasBudget = false, + IsActive = true, + IsHidden = false, + IsPromotedToDirectory = false, + IsPublicPage = false, + IsSensitive = false, + Name = "Coordinators", + RequiresApproval = false, + ShowCoordinatorsOnPublicPage = true, + Slug = "coordinators", + SystemTeamType = "Coordinators", + UpdatedAt = NodaTime.Instant.FromUnixTimeTicks(17702491570000000L) + }, + new + { + Id = new Guid("00000000-0000-0000-0001-000000000003"), + CreatedAt = NodaTime.Instant.FromUnixTimeTicks(17702491570000000L), + Description = "Board members with active role assignments", + HasBudget = false, + IsActive = true, + IsHidden = false, + IsPromotedToDirectory = false, + IsPublicPage = false, + IsSensitive = false, + Name = "Board", + RequiresApproval = false, + ShowCoordinatorsOnPublicPage = true, + Slug = "board", + SystemTeamType = "Board", + UpdatedAt = NodaTime.Instant.FromUnixTimeTicks(17702491570000000L) + }, + new + { + Id = new Guid("00000000-0000-0000-0001-000000000004"), + CreatedAt = NodaTime.Instant.FromUnixTimeTicks(17702491570000000L), + Description = "Voting members with approved asociado applications", + HasBudget = false, + IsActive = true, + IsHidden = false, + IsPromotedToDirectory = false, + IsPublicPage = false, + IsSensitive = false, + Name = "Asociados", + RequiresApproval = false, + ShowCoordinatorsOnPublicPage = true, + Slug = "asociados", + SystemTeamType = "Asociados", + UpdatedAt = NodaTime.Instant.FromUnixTimeTicks(17702491570000000L) + }, + new + { + Id = new Guid("00000000-0000-0000-0001-000000000005"), + CreatedAt = NodaTime.Instant.FromUnixTimeTicks(17702491570000000L), + Description = "Active contributors with approved colaborador applications", + HasBudget = false, + IsActive = true, + IsHidden = false, + IsPromotedToDirectory = false, + IsPublicPage = false, + IsSensitive = false, + Name = "Colaboradors", + RequiresApproval = false, + ShowCoordinatorsOnPublicPage = true, + Slug = "colaboradors", + SystemTeamType = "Colaboradors", + UpdatedAt = NodaTime.Instant.FromUnixTimeTicks(17702491570000000L) + }, + new + { + Id = new Guid("00000000-0000-0000-0001-000000000006"), + CreatedAt = NodaTime.Instant.FromUnixTimeTicks(17702491570000000L), + Description = "All active camp leads across all camps", + HasBudget = false, + IsActive = true, + IsHidden = false, + IsPromotedToDirectory = false, + IsPublicPage = false, + IsSensitive = false, + Name = "Barrio Leads", + RequiresApproval = false, + ShowCoordinatorsOnPublicPage = true, + Slug = "barrio-leads", + SystemTeamType = "BarrioLeads", + UpdatedAt = NodaTime.Instant.FromUnixTimeTicks(17702491570000000L) + }); + }); + + modelBuilder.Entity("Humans.Domain.Entities.TeamJoinRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Message") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("RequestedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ResolvedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ReviewNotes") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("ReviewedByUserId") + .HasColumnType("uuid"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("TeamId") + .HasColumnType("uuid"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ReviewedByUserId"); + + b.HasIndex("Status"); + + b.HasIndex("TeamId"); + + b.HasIndex("UserId"); + + b.HasIndex("TeamId", "UserId", "Status"); + + b.ToTable("team_join_requests", (string)null); + }); + + modelBuilder.Entity("Humans.Domain.Entities.TeamJoinRequestStateHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ChangedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ChangedByUserId") + .HasColumnType("uuid"); + + b.Property("Notes") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("TeamJoinRequestId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ChangedAt"); + + b.HasIndex("ChangedByUserId"); + + b.HasIndex("TeamJoinRequestId"); + + b.ToTable("team_join_request_state_history", (string)null); + }); + + modelBuilder.Entity("Humans.Domain.Entities.TeamMember", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("JoinedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LeftAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("TeamId") + .HasColumnType("uuid"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("Role"); + + b.HasIndex("UserId"); + + b.HasIndex("TeamId", "UserId") + .IsUnique() + .HasDatabaseName("IX_team_members_active_unique") + .HasFilter("\"LeftAt\" IS NULL"); + + b.ToTable("team_members", (string)null); + }); + + modelBuilder.Entity("Humans.Domain.Entities.TeamRoleAssignment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AssignedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("AssignedByUserId") + .HasColumnType("uuid"); + + b.Property("SlotIndex") + .HasColumnType("integer"); + + b.Property("TeamMemberId") + .HasColumnType("uuid"); + + b.Property("TeamRoleDefinitionId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AssignedByUserId"); + + b.HasIndex("TeamMemberId"); + + b.HasIndex("TeamRoleDefinitionId", "SlotIndex") + .IsUnique() + .HasDatabaseName("IX_team_role_assignments_definition_slot_unique"); + + b.HasIndex("TeamRoleDefinitionId", "TeamMemberId") + .IsUnique() + .HasDatabaseName("IX_team_role_assignments_definition_member_unique"); + + b.ToTable("team_role_assignments", (string)null); + }); + + modelBuilder.Entity("Humans.Domain.Entities.TeamRoleDefinition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("IsManagement") + .HasColumnType("boolean"); + + b.Property("IsPublic") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Period") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Priorities") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(500) + .HasColumnType("character varying(500)") + .HasDefaultValueSql("''"); + + b.Property("SlotCount") + .HasColumnType("integer"); + + b.Property("SortOrder") + .HasColumnType("integer"); + + b.Property("TeamId") + .HasColumnType("uuid"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("TeamId"); + + b.HasIndex("TeamId", "Name") + .IsUnique() + .HasDatabaseName("IX_team_role_definitions_team_name_unique"); + + b.ToTable("team_role_definitions", (string)null); + }); + + modelBuilder.Entity("Humans.Domain.Entities.TicketAttendee", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AttendeeEmail") + .HasMaxLength(320) + .HasColumnType("character varying(320)"); + + b.Property("AttendeeName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("MatchedUserId") + .HasColumnType("uuid"); + + b.Property("Price") + .HasPrecision(10, 2) + .HasColumnType("numeric(10,2)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("SyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("TicketOrderId") + .HasColumnType("uuid"); + + b.Property("TicketTypeName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("VendorEventId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("VendorTicketId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.HasKey("Id"); + + b.HasIndex("AttendeeEmail"); + + b.HasIndex("MatchedUserId"); + + b.HasIndex("TicketOrderId"); + + b.HasIndex("VendorTicketId") + .IsUnique(); + + b.ToTable("ticket_attendees", (string)null); + }); + + modelBuilder.Entity("Humans.Domain.Entities.TicketOrder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApplicationFee") + .HasPrecision(10, 2) + .HasColumnType("numeric(10,2)"); + + b.Property("BuyerEmail") + .IsRequired() + .HasMaxLength(320) + .HasColumnType("character varying(320)"); + + b.Property("BuyerName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Currency") + .IsRequired() + .HasMaxLength(3) + .HasColumnType("character varying(3)"); + + b.Property("DiscountAmount") + .HasPrecision(10, 2) + .HasColumnType("numeric(10,2)"); + + b.Property("DiscountCode") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("DonationAmount") + .HasPrecision(10, 2) + .HasColumnType("numeric(10,2)"); + + b.Property("MatchedUserId") + .HasColumnType("uuid"); + + b.Property("PaymentMethod") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PaymentMethodDetail") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PaymentStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("PurchasedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("StripeFee") + .HasPrecision(10, 2) + .HasColumnType("numeric(10,2)"); + + b.Property("StripePaymentIntentId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SyncedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("TotalAmount") + .HasPrecision(10, 2) + .HasColumnType("numeric(10,2)"); + + b.Property("VatAmount") + .HasPrecision(10, 2) + .HasColumnType("numeric(10,2)"); + + b.Property("VendorDashboardUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("VendorEventId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("VendorOrderId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.HasKey("Id"); + + b.HasIndex("BuyerEmail"); + + b.HasIndex("MatchedUserId"); + + b.HasIndex("PaymentMethod"); + + b.HasIndex("PurchasedAt"); + + b.HasIndex("VendorOrderId") + .IsUnique(); + + b.ToTable("ticket_orders", (string)null); + }); + + modelBuilder.Entity("Humans.Domain.Entities.TicketSyncState", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("LastError") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("LastSyncAt") + .HasColumnType("timestamp with time zone"); + + b.Property("StatusChangedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("SyncStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("VendorEventId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.HasKey("Id"); + + b.ToTable("ticket_sync_state", (string)null); + + b.HasData( + new + { + Id = 1, + SyncStatus = "Idle", + VendorEventId = "" + }); + }); + + modelBuilder.Entity("Humans.Domain.Entities.TicketingProjection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AverageTicketPrice") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("BudgetGroupId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DailySalesRate") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("EventDate") + .HasColumnType("date"); + + b.Property("InitialSalesCount") + .HasColumnType("integer"); + + b.Property("StartDate") + .HasColumnType("date"); + + b.Property("StripeFeeFixed") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("StripeFeePercent") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("TicketTailorFeePercent") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("VatRate") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("BudgetGroupId") + .IsUnique(); + + b.ToTable("ticketing_projections", (string)null); + }); + + modelBuilder.Entity("Humans.Domain.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("ContactSource") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletionEligibleAfter") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletionRequestedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeletionScheduledFor") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("ExternalSourceId") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("GoogleEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("GoogleEmailStatus") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasDefaultValue("Unknown"); + + b.Property("ICalToken") + .HasColumnType("uuid"); + + b.Property("LastConsentReminderSentAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastLoginAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("MagicLinkSentAt") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("PreferredLanguage") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(10) + .HasColumnType("character varying(10)") + .HasDefaultValue("en"); + + b.Property("ProfilePictureUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("SuppressScheduleChangeEmails") + .HasColumnType("boolean"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UnsubscribedFromCampaigns") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("Email"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.HasIndex("ContactSource", "ExternalSourceId") + .HasFilter("\"ExternalSourceId\" IS NOT NULL"); + + b.ToTable("users", (string)null); + }); + + modelBuilder.Entity("Humans.Domain.Entities.UserEmail", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DisplayOrder") + .HasColumnType("integer"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("IsNotificationTarget") + .HasColumnType("boolean"); + + b.Property("IsOAuth") + .HasColumnType("boolean"); + + b.Property("IsVerified") + .HasColumnType("boolean"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("VerificationSentAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Visibility") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique() + .HasFilter("\"IsVerified\" = true"); + + b.HasIndex("UserId"); + + b.ToTable("user_emails", (string)null); + }); + + modelBuilder.Entity("Humans.Domain.Entities.VolunteerEventProfile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Allergies") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("AllergyOtherText") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DietaryPreference") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("IntoleranceOtherText") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Intolerances") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Languages") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("MedicalConditions") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("Quirks") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Skills") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("volunteer_event_profiles", (string)null); + }); + + modelBuilder.Entity("Humans.Domain.Entities.VolunteerHistoryEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("date"); + + b.Property("Description") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("EventName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ProfileId") + .HasColumnType("uuid"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("ProfileId"); + + b.ToTable("volunteer_history_entries", (string)null); + }); + + modelBuilder.Entity("Humans.Domain.Entities.VolunteerTagPreference", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ShiftTagId") + .HasColumnType("uuid"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ShiftTagId"); + + b.HasIndex("UserId"); + + b.HasIndex("UserId", "ShiftTagId") + .IsUnique() + .HasDatabaseName("IX_volunteer_tag_preferences_user_tag_unique"); + + b.ToTable("volunteer_tag_preferences", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("FriendlyName") + .HasColumnType("text"); + + b.Property("Xml") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("DataProtectionKeys"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("roles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("role_claims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("user_claims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("user_logins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("user_roles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("user_tokens", (string)null); + }); + + modelBuilder.Entity("RotaShiftTag", b => + { + b.Property("RotaId") + .HasColumnType("uuid"); + + b.Property("ShiftTagId") + .HasColumnType("uuid"); + + b.HasKey("RotaId", "ShiftTagId"); + + b.HasIndex("ShiftTagId"); + + b.ToTable("rota_shift_tags", (string)null); + }); + + modelBuilder.Entity("Humans.Domain.Entities.AccountMergeRequest", b => + { + b.HasOne("Humans.Domain.Entities.User", "ResolvedByUser") + .WithMany() + .HasForeignKey("ResolvedByUserId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Humans.Domain.Entities.User", "SourceUser") + .WithMany() + .HasForeignKey("SourceUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Humans.Domain.Entities.User", "TargetUser") + .WithMany() + .HasForeignKey("TargetUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ResolvedByUser"); + + b.Navigation("SourceUser"); + + b.Navigation("TargetUser"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.Application", b => + { + b.HasOne("Humans.Domain.Entities.User", "ReviewedByUser") + .WithMany() + .HasForeignKey("ReviewedByUserId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Humans.Domain.Entities.User", "User") + .WithMany("Applications") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ReviewedByUser"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.ApplicationStateHistory", b => + { + b.HasOne("Humans.Domain.Entities.Application", "Application") + .WithMany("StateHistory") + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Humans.Domain.Entities.User", "ChangedByUser") + .WithMany() + .HasForeignKey("ChangedByUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Application"); + + b.Navigation("ChangedByUser"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.AuditLogEntry", b => + { + b.HasOne("Humans.Domain.Entities.User", "ActorUser") + .WithMany() + .HasForeignKey("ActorUserId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Humans.Domain.Entities.GoogleResource", "Resource") + .WithMany() + .HasForeignKey("ResourceId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ActorUser"); + + b.Navigation("Resource"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.BoardVote", b => + { + b.HasOne("Humans.Domain.Entities.Application", "Application") + .WithMany("BoardVotes") + .HasForeignKey("ApplicationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Humans.Domain.Entities.User", "BoardMemberUser") + .WithMany() + .HasForeignKey("BoardMemberUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Application"); + + b.Navigation("BoardMemberUser"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.BudgetAuditLog", b => + { + b.HasOne("Humans.Domain.Entities.User", "ActorUser") + .WithMany() + .HasForeignKey("ActorUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Humans.Domain.Entities.BudgetYear", "BudgetYear") + .WithMany("AuditLogs") + .HasForeignKey("BudgetYearId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("ActorUser"); + + b.Navigation("BudgetYear"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.BudgetCategory", b => + { + b.HasOne("Humans.Domain.Entities.BudgetGroup", "BudgetGroup") + .WithMany("Categories") + .HasForeignKey("BudgetGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Humans.Domain.Entities.Team", "Team") + .WithMany() + .HasForeignKey("TeamId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("BudgetGroup"); + + b.Navigation("Team"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.BudgetGroup", b => + { + b.HasOne("Humans.Domain.Entities.BudgetYear", "BudgetYear") + .WithMany("Groups") + .HasForeignKey("BudgetYearId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("BudgetYear"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.BudgetLineItem", b => + { + b.HasOne("Humans.Domain.Entities.BudgetCategory", "BudgetCategory") + .WithMany("LineItems") + .HasForeignKey("BudgetCategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Humans.Domain.Entities.Team", "ResponsibleTeam") + .WithMany() + .HasForeignKey("ResponsibleTeamId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("BudgetCategory"); + + b.Navigation("ResponsibleTeam"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.Camp", b => + { + b.HasOne("Humans.Domain.Entities.User", "CreatedByUser") + .WithMany() + .HasForeignKey("CreatedByUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("CreatedByUser"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.CampHistoricalName", b => + { + b.HasOne("Humans.Domain.Entities.Camp", "Camp") + .WithMany("HistoricalNames") + .HasForeignKey("CampId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Camp"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.CampImage", b => + { + b.HasOne("Humans.Domain.Entities.Camp", "Camp") + .WithMany("Images") + .HasForeignKey("CampId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Camp"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.CampLead", b => + { + b.HasOne("Humans.Domain.Entities.Camp", "Camp") + .WithMany("Leads") + .HasForeignKey("CampId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Humans.Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Camp"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.CampPolygon", b => + { + b.HasOne("Humans.Domain.Entities.CampSeason", "CampSeason") + .WithMany() + .HasForeignKey("CampSeasonId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Humans.Domain.Entities.User", "LastModifiedByUser") + .WithMany() + .HasForeignKey("LastModifiedByUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("CampSeason"); + + b.Navigation("LastModifiedByUser"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.CampPolygonHistory", b => + { + b.HasOne("Humans.Domain.Entities.CampSeason", "CampSeason") + .WithMany() + .HasForeignKey("CampSeasonId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Humans.Domain.Entities.User", "ModifiedByUser") + .WithMany() + .HasForeignKey("ModifiedByUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("CampSeason"); + + b.Navigation("ModifiedByUser"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.CampSeason", b => + { + b.HasOne("Humans.Domain.Entities.Camp", "Camp") + .WithMany("Seasons") + .HasForeignKey("CampId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Humans.Domain.Entities.User", "ReviewedByUser") + .WithMany() + .HasForeignKey("ReviewedByUserId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Camp"); + + b.Navigation("ReviewedByUser"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.Campaign", b => + { + b.HasOne("Humans.Domain.Entities.User", "CreatedByUser") + .WithMany() + .HasForeignKey("CreatedByUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("CreatedByUser"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.CampaignCode", b => + { + b.HasOne("Humans.Domain.Entities.Campaign", "Campaign") + .WithMany("Codes") + .HasForeignKey("CampaignId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Campaign"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.CampaignGrant", b => + { + b.HasOne("Humans.Domain.Entities.CampaignCode", "Code") + .WithOne("Grant") + .HasForeignKey("Humans.Domain.Entities.CampaignGrant", "CampaignCodeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Humans.Domain.Entities.Campaign", "Campaign") + .WithMany("Grants") + .HasForeignKey("CampaignId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Humans.Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Campaign"); + + b.Navigation("Code"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.CommunicationPreference", b => + { + b.HasOne("Humans.Domain.Entities.User", "User") + .WithMany("CommunicationPreferences") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.ConsentRecord", b => + { + b.HasOne("Humans.Domain.Entities.DocumentVersion", "DocumentVersion") + .WithMany("ConsentRecords") + .HasForeignKey("DocumentVersionId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Humans.Domain.Entities.User", "User") + .WithMany("ConsentRecords") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("DocumentVersion"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.ContactField", b => + { + b.HasOne("Humans.Domain.Entities.Profile", "Profile") + .WithMany("ContactFields") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.DocumentVersion", b => + { + b.HasOne("Humans.Domain.Entities.LegalDocument", "LegalDocument") + .WithMany("Versions") + .HasForeignKey("LegalDocumentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("LegalDocument"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.EmailOutboxMessage", b => + { + b.HasOne("Humans.Domain.Entities.CampaignGrant", "CampaignGrant") + .WithMany("OutboxMessages") + .HasForeignKey("CampaignGrantId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Humans.Domain.Entities.ShiftSignup", "ShiftSignup") + .WithMany() + .HasForeignKey("ShiftSignupId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Humans.Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("CampaignGrant"); + + b.Navigation("ShiftSignup"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.EventParticipation", b => + { + b.HasOne("Humans.Domain.Entities.User", "User") + .WithMany("EventParticipations") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.FeedbackMessage", b => + { + b.HasOne("Humans.Domain.Entities.FeedbackReport", "FeedbackReport") + .WithMany("Messages") + .HasForeignKey("FeedbackReportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Humans.Domain.Entities.User", "SenderUser") + .WithMany() + .HasForeignKey("SenderUserId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("FeedbackReport"); + + b.Navigation("SenderUser"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.FeedbackReport", b => + { + b.HasOne("Humans.Domain.Entities.Team", "AssignedToTeam") + .WithMany() + .HasForeignKey("AssignedToTeamId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Humans.Domain.Entities.User", "AssignedToUser") + .WithMany() + .HasForeignKey("AssignedToUserId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Humans.Domain.Entities.User", "ResolvedByUser") + .WithMany() + .HasForeignKey("ResolvedByUserId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Humans.Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AssignedToTeam"); + + b.Navigation("AssignedToUser"); + + b.Navigation("ResolvedByUser"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.GeneralAvailability", b => + { + b.HasOne("Humans.Domain.Entities.EventSettings", "EventSettings") + .WithMany() + .HasForeignKey("EventSettingsId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Humans.Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("EventSettings"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.GoogleResource", b => + { + b.HasOne("Humans.Domain.Entities.Team", "Team") + .WithMany() + .HasForeignKey("TeamId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Team"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.GoogleSyncOutboxEvent", b => + { + b.HasOne("Humans.Domain.Entities.Team", null) + .WithMany() + .HasForeignKey("TeamId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Humans.Domain.Entities.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Humans.Domain.Entities.LegalDocument", b => + { + b.HasOne("Humans.Domain.Entities.Team", "Team") + .WithMany("LegalDocuments") + .HasForeignKey("TeamId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Team"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.Notification", b => + { + b.HasOne("Humans.Domain.Entities.User", "ResolvedByUser") + .WithMany() + .HasForeignKey("ResolvedByUserId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ResolvedByUser"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.NotificationRecipient", b => + { + b.HasOne("Humans.Domain.Entities.Notification", "Notification") + .WithMany("Recipients") + .HasForeignKey("NotificationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Humans.Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Notification"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.Profile", b => + { + b.HasOne("Humans.Domain.Entities.User", "User") + .WithOne("Profile") + .HasForeignKey("Humans.Domain.Entities.Profile", "UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.ProfileLanguage", b => + { + b.HasOne("Humans.Domain.Entities.Profile", "Profile") + .WithMany("Languages") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.RoleAssignment", b => + { + b.HasOne("Humans.Domain.Entities.User", "CreatedByUser") + .WithMany() + .HasForeignKey("CreatedByUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Humans.Domain.Entities.User", "User") + .WithMany("RoleAssignments") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CreatedByUser"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.Rota", b => + { + b.HasOne("Humans.Domain.Entities.EventSettings", "EventSettings") + .WithMany("Rotas") + .HasForeignKey("EventSettingsId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Humans.Domain.Entities.Team", "Team") + .WithMany() + .HasForeignKey("TeamId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("EventSettings"); + + b.Navigation("Team"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.Shift", b => + { + b.HasOne("Humans.Domain.Entities.Rota", "Rota") + .WithMany("Shifts") + .HasForeignKey("RotaId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Rota"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.ShiftSignup", b => + { + b.HasOne("Humans.Domain.Entities.User", "EnrolledByUser") + .WithMany() + .HasForeignKey("EnrolledByUserId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Humans.Domain.Entities.User", "ReviewedByUser") + .WithMany() + .HasForeignKey("ReviewedByUserId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Humans.Domain.Entities.Shift", "Shift") + .WithMany("ShiftSignups") + .HasForeignKey("ShiftId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Humans.Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("EnrolledByUser"); + + b.Navigation("ReviewedByUser"); + + b.Navigation("Shift"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.SyncServiceSettings", b => + { + b.HasOne("Humans.Domain.Entities.User", "UpdatedByUser") + .WithMany() + .HasForeignKey("UpdatedByUserId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("UpdatedByUser"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.Team", b => + { + b.HasOne("Humans.Domain.Entities.Team", "ParentTeam") + .WithMany("ChildTeams") + .HasForeignKey("ParentTeamId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("ParentTeam"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.TeamJoinRequest", b => + { + b.HasOne("Humans.Domain.Entities.User", "ReviewedByUser") + .WithMany() + .HasForeignKey("ReviewedByUserId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Humans.Domain.Entities.Team", "Team") + .WithMany("JoinRequests") + .HasForeignKey("TeamId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Humans.Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ReviewedByUser"); + + b.Navigation("Team"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.TeamJoinRequestStateHistory", b => + { + b.HasOne("Humans.Domain.Entities.User", "ChangedByUser") + .WithMany() + .HasForeignKey("ChangedByUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Humans.Domain.Entities.TeamJoinRequest", "TeamJoinRequest") + .WithMany("StateHistory") + .HasForeignKey("TeamJoinRequestId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ChangedByUser"); + + b.Navigation("TeamJoinRequest"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.TeamMember", b => + { + b.HasOne("Humans.Domain.Entities.Team", "Team") + .WithMany("Members") + .HasForeignKey("TeamId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Humans.Domain.Entities.User", "User") + .WithMany("TeamMemberships") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Team"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.TeamRoleAssignment", b => + { + b.HasOne("Humans.Domain.Entities.User", "AssignedByUser") + .WithMany() + .HasForeignKey("AssignedByUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Humans.Domain.Entities.TeamMember", "TeamMember") + .WithMany("RoleAssignments") + .HasForeignKey("TeamMemberId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Humans.Domain.Entities.TeamRoleDefinition", "TeamRoleDefinition") + .WithMany("Assignments") + .HasForeignKey("TeamRoleDefinitionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AssignedByUser"); + + b.Navigation("TeamMember"); + + b.Navigation("TeamRoleDefinition"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.TeamRoleDefinition", b => + { + b.HasOne("Humans.Domain.Entities.Team", "Team") + .WithMany("RoleDefinitions") + .HasForeignKey("TeamId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Team"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.TicketAttendee", b => + { + b.HasOne("Humans.Domain.Entities.User", "MatchedUser") + .WithMany() + .HasForeignKey("MatchedUserId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Humans.Domain.Entities.TicketOrder", "TicketOrder") + .WithMany("Attendees") + .HasForeignKey("TicketOrderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MatchedUser"); + + b.Navigation("TicketOrder"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.TicketOrder", b => + { + b.HasOne("Humans.Domain.Entities.User", "MatchedUser") + .WithMany() + .HasForeignKey("MatchedUserId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("MatchedUser"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.TicketingProjection", b => + { + b.HasOne("Humans.Domain.Entities.BudgetGroup", "BudgetGroup") + .WithOne("TicketingProjection") + .HasForeignKey("Humans.Domain.Entities.TicketingProjection", "BudgetGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("BudgetGroup"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.UserEmail", b => + { + b.HasOne("Humans.Domain.Entities.User", "User") + .WithMany("UserEmails") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.VolunteerEventProfile", b => + { + b.HasOne("Humans.Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.VolunteerHistoryEntry", b => + { + b.HasOne("Humans.Domain.Entities.Profile", "Profile") + .WithMany("VolunteerHistory") + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.VolunteerTagPreference", b => + { + b.HasOne("Humans.Domain.Entities.ShiftTag", "ShiftTag") + .WithMany("VolunteerPreferences") + .HasForeignKey("ShiftTagId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Humans.Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ShiftTag"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Humans.Domain.Entities.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Humans.Domain.Entities.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Humans.Domain.Entities.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Humans.Domain.Entities.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("RotaShiftTag", b => + { + b.HasOne("Humans.Domain.Entities.Rota", null) + .WithMany() + .HasForeignKey("RotaId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Humans.Domain.Entities.ShiftTag", null) + .WithMany() + .HasForeignKey("ShiftTagId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Humans.Domain.Entities.Application", b => + { + b.Navigation("BoardVotes"); + + b.Navigation("StateHistory"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.BudgetCategory", b => + { + b.Navigation("LineItems"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.BudgetGroup", b => + { + b.Navigation("Categories"); + + b.Navigation("TicketingProjection"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.BudgetYear", b => + { + b.Navigation("AuditLogs"); + + b.Navigation("Groups"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.Camp", b => + { + b.Navigation("HistoricalNames"); + + b.Navigation("Images"); + + b.Navigation("Leads"); + + b.Navigation("Seasons"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.Campaign", b => + { + b.Navigation("Codes"); + + b.Navigation("Grants"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.CampaignCode", b => + { + b.Navigation("Grant"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.CampaignGrant", b => + { + b.Navigation("OutboxMessages"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.DocumentVersion", b => + { + b.Navigation("ConsentRecords"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.EventSettings", b => + { + b.Navigation("Rotas"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.FeedbackReport", b => + { + b.Navigation("Messages"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.LegalDocument", b => + { + b.Navigation("Versions"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.Notification", b => + { + b.Navigation("Recipients"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.Profile", b => + { + b.Navigation("ContactFields"); + + b.Navigation("Languages"); + + b.Navigation("VolunteerHistory"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.Rota", b => + { + b.Navigation("Shifts"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.Shift", b => + { + b.Navigation("ShiftSignups"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.ShiftTag", b => + { + b.Navigation("VolunteerPreferences"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.Team", b => + { + b.Navigation("ChildTeams"); + + b.Navigation("JoinRequests"); + + b.Navigation("LegalDocuments"); + + b.Navigation("Members"); + + b.Navigation("RoleDefinitions"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.TeamJoinRequest", b => + { + b.Navigation("StateHistory"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.TeamMember", b => + { + b.Navigation("RoleAssignments"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.TeamRoleDefinition", b => + { + b.Navigation("Assignments"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.TicketOrder", b => + { + b.Navigation("Attendees"); + }); + + modelBuilder.Entity("Humans.Domain.Entities.User", b => + { + b.Navigation("Applications"); + + b.Navigation("CommunicationPreferences"); + + b.Navigation("ConsentRecords"); + + b.Navigation("EventParticipations"); + + b.Navigation("Profile"); + + b.Navigation("RoleAssignments"); + + b.Navigation("TeamMemberships"); + + b.Navigation("UserEmails"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Humans.Infrastructure/Migrations/20260414231712_AddIsPromotedToDirectory.cs b/src/Humans.Infrastructure/Migrations/20260414231712_AddIsPromotedToDirectory.cs new file mode 100644 index 000000000..1ca019171 --- /dev/null +++ b/src/Humans.Infrastructure/Migrations/20260414231712_AddIsPromotedToDirectory.cs @@ -0,0 +1,72 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Humans.Infrastructure.Migrations +{ + /// + public partial class AddIsPromotedToDirectory : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "IsPromotedToDirectory", + table: "teams", + type: "boolean", + nullable: false, + defaultValue: false); + + migrationBuilder.UpdateData( + table: "teams", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0001-000000000001"), + column: "IsPromotedToDirectory", + value: false); + + migrationBuilder.UpdateData( + table: "teams", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0001-000000000002"), + column: "IsPromotedToDirectory", + value: false); + + migrationBuilder.UpdateData( + table: "teams", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0001-000000000003"), + column: "IsPromotedToDirectory", + value: false); + + migrationBuilder.UpdateData( + table: "teams", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0001-000000000004"), + column: "IsPromotedToDirectory", + value: false); + + migrationBuilder.UpdateData( + table: "teams", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0001-000000000005"), + column: "IsPromotedToDirectory", + value: false); + + migrationBuilder.UpdateData( + table: "teams", + keyColumn: "Id", + keyValue: new Guid("00000000-0000-0000-0001-000000000006"), + column: "IsPromotedToDirectory", + value: false); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "IsPromotedToDirectory", + table: "teams"); + } + } +} diff --git a/src/Humans.Infrastructure/Migrations/HumansDbContextModelSnapshot.cs b/src/Humans.Infrastructure/Migrations/HumansDbContextModelSnapshot.cs index 25b74ba7f..81e473943 100644 --- a/src/Humans.Infrastructure/Migrations/HumansDbContextModelSnapshot.cs +++ b/src/Humans.Infrastructure/Migrations/HumansDbContextModelSnapshot.cs @@ -2405,6 +2405,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("IsHidden") .HasColumnType("boolean"); + b.Property("IsPromotedToDirectory") + .HasColumnType("boolean"); + b.Property("IsPublicPage") .HasColumnType("boolean"); @@ -2482,6 +2485,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) HasBudget = false, IsActive = true, IsHidden = false, + IsPromotedToDirectory = false, IsPublicPage = false, IsSensitive = false, Name = "Volunteers", @@ -2499,6 +2503,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) HasBudget = false, IsActive = true, IsHidden = false, + IsPromotedToDirectory = false, IsPublicPage = false, IsSensitive = false, Name = "Coordinators", @@ -2516,6 +2521,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) HasBudget = false, IsActive = true, IsHidden = false, + IsPromotedToDirectory = false, IsPublicPage = false, IsSensitive = false, Name = "Board", @@ -2533,6 +2539,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) HasBudget = false, IsActive = true, IsHidden = false, + IsPromotedToDirectory = false, IsPublicPage = false, IsSensitive = false, Name = "Asociados", @@ -2550,6 +2557,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) HasBudget = false, IsActive = true, IsHidden = false, + IsPromotedToDirectory = false, IsPublicPage = false, IsSensitive = false, Name = "Colaboradors", @@ -2567,6 +2575,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) HasBudget = false, IsActive = true, IsHidden = false, + IsPromotedToDirectory = false, IsPublicPage = false, IsSensitive = false, Name = "Barrio Leads", diff --git a/src/Humans.Infrastructure/Services/TeamService.cs b/src/Humans.Infrastructure/Services/TeamService.cs index 3eba32087..e692dd268 100644 --- a/src/Humans.Infrastructure/Services/TeamService.cs +++ b/src/Humans.Infrastructure/Services/TeamService.cs @@ -139,7 +139,7 @@ public async Task CreateTeamAsync( UpsertCachedTeam(new CachedTeam(team.Id, team.Name, team.Description, team.Slug, team.IsSystemTeam, team.SystemTeamType, team.RequiresApproval, team.IsPublicPage, team.IsHidden, - team.CreatedAt, [], ParentTeamId: parentTeamId)); + team.IsPromotedToDirectory, team.CreatedAt, [], ParentTeamId: parentTeamId)); _logger.LogInformation("Created team {TeamName} with slug {Slug}", name, slug); return team; } @@ -218,7 +218,8 @@ public async Task GetTeamDirectoryAsync( if (!userId.HasValue) { var publicDepartments = cachedTeams.Values - .Where(t => t.IsPublicPage && !t.IsSystemTeam && !t.IsHidden && t.ParentTeamId is null) + .Where(t => t.IsPublicPage && !t.IsSystemTeam && !t.IsHidden + && (t.ParentTeamId is null || t.IsPromotedToDirectory)) .OrderBy(t => t.Name, StringComparer.OrdinalIgnoreCase) .Select(t => CreateDirectorySummary(t, cachedTeams, userId)) .ToList(); @@ -241,7 +242,10 @@ public async Task GetTeamDirectoryAsync( ? cachedTeams.Values : cachedTeams.Values.Where(t => !t.IsHidden); - var summaries = visibleTeams + var directoryTeams = visibleTeams + .Where(t => t.ParentTeamId is null || t.IsPromotedToDirectory); + + var summaries = directoryTeams .Select(t => CreateDirectorySummary(t, cachedTeams, userId)) .ToList(); @@ -439,6 +443,7 @@ public async Task UpdateTeamAsync( bool? hasBudget = null, bool? isHidden = null, bool? isSensitive = null, + bool? isPromotedToDirectory = null, CancellationToken cancellationToken = default) { var team = await _dbContext.Teams.FindAsync(new object[] { teamId }, cancellationToken) @@ -534,6 +539,8 @@ public async Task UpdateTeamAsync( team.IsHidden = isHidden.Value; if (isSensitive.HasValue) team.IsSensitive = isSensitive.Value; + if (isPromotedToDirectory.HasValue) + team.IsPromotedToDirectory = isPromotedToDirectory.Value; if (team.IsSystemTeam || parentTeamId.HasValue) { team.IsPublicPage = false; @@ -2161,6 +2168,7 @@ private async Task> GetCachedTeamsAsync(C RequiresApproval: team.RequiresApproval, IsPublicPage: team.IsPublicPage, IsHidden: team.IsHidden, + IsPromotedToDirectory: team.IsPromotedToDirectory, CreatedAt: team.CreatedAt, Members: team.Members .Where(m => m.LeftAt is null) diff --git a/src/Humans.Web/Controllers/TeamController.cs b/src/Humans.Web/Controllers/TeamController.cs index d2a64a63b..c286cdaf5 100644 --- a/src/Humans.Web/Controllers/TeamController.cs +++ b/src/Humans.Web/Controllers/TeamController.cs @@ -760,6 +760,7 @@ public async Task EditTeam(Guid id, CancellationToken cancellatio HasBudget = team.HasBudget, IsHidden = team.IsHidden, IsSensitive = team.IsSensitive, + IsPromotedToDirectory = team.IsPromotedToDirectory, ParentTeamId = team.ParentTeamId, EligibleParents = await GetEligibleParentTeamsAsync(excludeTeamId: id, cancellationToken) }; @@ -785,7 +786,7 @@ public async Task EditTeam(Guid id, EditTeamViewModel model) try { - await _teamService.UpdateTeamAsync(id, model.Name, model.Description, model.RequiresApproval, model.IsActive, model.ParentTeamId, model.GoogleGroupPrefix, model.CustomSlug, model.HasBudget, model.IsHidden, model.IsSensitive); + await _teamService.UpdateTeamAsync(id, model.Name, model.Description, model.RequiresApproval, model.IsActive, model.ParentTeamId, model.GoogleGroupPrefix, model.CustomSlug, model.HasBudget, model.IsHidden, model.IsSensitive, model.IsPromotedToDirectory); var currentUser = await GetCurrentUserAsync(); _logger.LogInformation("Admin {AdminId} updated team {TeamId}", currentUser?.Id, id); diff --git a/src/Humans.Web/Models/TeamViewModels.cs b/src/Humans.Web/Models/TeamViewModels.cs index 5493fd292..fd587c67a 100644 --- a/src/Humans.Web/Models/TeamViewModels.cs +++ b/src/Humans.Web/Models/TeamViewModels.cs @@ -223,6 +223,7 @@ public class EditTeamViewModel : TeamFormViewModelBase public bool IsSystemTeam { get; set; } public bool HasBudget { get; set; } public bool IsSensitive { get; set; } + public bool IsPromotedToDirectory { get; set; } } public class EditTeamPageViewModel diff --git a/src/Humans.Web/Views/Team/EditTeam.cshtml b/src/Humans.Web/Views/Team/EditTeam.cshtml index 7c6e3986f..a371cbd83 100644 --- a/src/Humans.Web/Views/Team/EditTeam.cshtml +++ b/src/Humans.Web/Views/Team/EditTeam.cshtml @@ -69,6 +69,15 @@ } + @if (Model.ParentTeamId.HasValue) + { +
+ + +
When enabled, this sub-team also appears on the main Teams directory page.
+
+ } +
From 5bd95f3293f9959a0dd8fbc965726f63427b324e Mon Sep 17 00:00:00 2001 From: Peter Drier Date: Mon, 13 Apr 2026 18:09:14 +0200 Subject: [PATCH 2/2] Show promoted subteams in anonymous /Teams directory Subteams cannot have IsPublicPage (forced false in UpdateTeamAsync), so the anon filter that required IsPublicPage excluded promoted subteams entirely. Treat IsPromotedToDirectory as the subteam-equivalent opt-in, and let anon visitors reach a promoted subteam's detail page so the directory link works. Addresses review on PR #187. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../Services/TeamService.cs | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/Humans.Infrastructure/Services/TeamService.cs b/src/Humans.Infrastructure/Services/TeamService.cs index e692dd268..8e2e3babd 100644 --- a/src/Humans.Infrastructure/Services/TeamService.cs +++ b/src/Humans.Infrastructure/Services/TeamService.cs @@ -217,9 +217,13 @@ public async Task GetTeamDirectoryAsync( if (!userId.HasValue) { + // Top-level teams opt into the public directory via IsPublicPage. + // Subteams cannot set IsPublicPage (forced false in UpdateTeamAsync); + // their opt-in is IsPromotedToDirectory. var publicDepartments = cachedTeams.Values - .Where(t => t.IsPublicPage && !t.IsSystemTeam && !t.IsHidden - && (t.ParentTeamId is null || t.IsPromotedToDirectory)) + .Where(t => !t.IsSystemTeam && !t.IsHidden + && ((t.ParentTeamId is null && t.IsPublicPage) + || (t.ParentTeamId is not null && t.IsPromotedToDirectory))) .OrderBy(t => t.Name, StringComparer.OrdinalIgnoreCase) .Select(t => CreateDirectorySummary(t, cachedTeams, userId)) .ToList(); @@ -283,10 +287,17 @@ public async Task GetTeamDirectoryAsync( return null; } - if (!userId.HasValue && - (!team.IsPublicPage || team.IsHidden || team.IsSystemTeam || team.ParentTeamId.HasValue)) + if (!userId.HasValue) { - return null; + // Anonymous visitors can see top-level teams with IsPublicPage, + // or subteams that have been promoted to the directory. + var isAnonymouslyVisible = !team.IsHidden && !team.IsSystemTeam + && ((team.ParentTeamId is null && team.IsPublicPage) + || (team.ParentTeamId is not null && team.IsPromotedToDirectory)); + if (!isAnonymouslyVisible) + { + return null; + } } var activeMembers = team.Members