diff --git a/backend/src/Socialize.Api/Migrations/20260508030349_SimplifyReleaseUpdates.Designer.cs b/backend/src/Socialize.Api/Migrations/20260508030349_SimplifyReleaseUpdates.Designer.cs new file mode 100644 index 00000000..d02b9605 --- /dev/null +++ b/backend/src/Socialize.Api/Migrations/20260508030349_SimplifyReleaseUpdates.Designer.cs @@ -0,0 +1,2591 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Socialize.Api.Data; + +#nullable disable + +namespace Socialize.Api.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20260508030349_SimplifyReleaseUpdates")] + partial class SimplifyReleaseUpdates + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + 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("AspNetRoleClaims", (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("AspNetUserClaims", (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("AspNetUserLogins", (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("AspNetUserRoles", (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("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Approvals.Data.ApprovalDecision", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApprovalRequestId") + .HasColumnType("uuid"); + + b.Property("Comment") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("DecidedByEmail") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("DecidedByName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("DecidedByUserId") + .HasColumnType("uuid"); + + b.Property("Decision") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.HasKey("Id"); + + b.HasIndex("ApprovalRequestId"); + + b.ToTable("ApprovalDecisions", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Approvals.Data.ApprovalRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccessToken") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ContentItemId") + .HasColumnType("uuid"); + + b.Property("DueAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RequestedByUserId") + .HasColumnType("uuid"); + + b.Property("ReviewerEmail") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ReviewerName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("SentAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("Stage") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("State") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("WorkflowInstanceId") + .HasColumnType("uuid"); + + b.Property("WorkflowStepRequiredApproverCount") + .HasColumnType("integer"); + + b.Property("WorkflowStepSortOrder") + .HasColumnType("integer"); + + b.Property("WorkflowStepTargetType") + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("WorkflowStepTargetValue") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("WorkspaceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ContentItemId"); + + b.HasIndex("ReviewerEmail"); + + b.HasIndex("WorkflowInstanceId"); + + b.HasIndex("WorkspaceId"); + + b.ToTable("ApprovalRequests", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Approvals.Data.ApprovalWorkflowInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApprovalMode") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ContentItemId") + .HasColumnType("uuid"); + + b.Property("StartedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("State") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("WorkspaceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ContentItemId"); + + b.HasIndex("WorkspaceId"); + + b.HasIndex("ContentItemId", "State") + .IsUnique() + .HasFilter("\"State\" = 'Pending'"); + + b.ToTable("ApprovalWorkflowInstances", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Approvals.Data.WorkspaceApprovalStepConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("RequiredApproverCount") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(1); + + b.Property("SortOrder") + .HasColumnType("integer"); + + b.Property("TargetType") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("TargetValue") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("WorkspaceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("WorkspaceId"); + + b.HasIndex("WorkspaceId", "SortOrder") + .IsUnique(); + + b.ToTable("WorkspaceApprovalStepConfigurations", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Assets.Data.Asset", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AssetType") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("ContentItemId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("CurrentRevisionNumber") + .HasColumnType("integer"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("GoogleDriveFileId") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("GoogleDriveLink") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("PreviewUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("SourceType") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("WorkspaceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ContentItemId"); + + b.HasIndex("WorkspaceId"); + + b.ToTable("Assets", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Assets.Data.AssetRevision", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AssetId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("CreatedByUserId") + .HasColumnType("uuid"); + + b.Property("Notes") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("PreviewUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("RevisionNumber") + .HasColumnType("integer"); + + b.Property("SourceReference") + .IsRequired() + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.HasKey("Id"); + + b.HasIndex("AssetId"); + + b.HasIndex("AssetId", "RevisionNumber") + .IsUnique(); + + b.ToTable("AssetRevisions", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.CalendarIntegrations.Data.CalendarCatalogEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Category") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Country") + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("CultureOrReligion") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("DefaultColor") + .IsRequired() + .HasMaxLength(16) + .HasColumnType("character varying(16)"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("Language") + .IsRequired() + .HasMaxLength(16) + .HasColumnType("character varying(16)"); + + b.Property("ProviderName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("Region") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("SourceUrl") + .IsRequired() + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("TrustLevel") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.HasKey("Id"); + + b.HasIndex("Category"); + + b.HasIndex("Country"); + + b.HasIndex("ProviderName"); + + b.ToTable("CalendarCatalogEntries", (string)null); + + b.HasData( + new + { + Id = new Guid("10000000-0000-0000-0000-000000000001"), + Category = "public-holiday", + Country = "US", + CreatedAt = new DateTimeOffset(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + DefaultColor = "#2F80ED", + Description = "Federal public holiday calendar for the United States.", + Language = "en", + ProviderName = "Nager.Date", + SourceUrl = "https://date.nager.at/api/v3/PublicHolidays/2026/US", + Title = "United States Public Holidays", + TrustLevel = "Verified" + }, + new + { + Id = new Guid("10000000-0000-0000-0000-000000000002"), + Category = "public-holiday", + Country = "CA", + CreatedAt = new DateTimeOffset(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + DefaultColor = "#2F80ED", + Description = "Public holiday calendar for Canada.", + Language = "en", + ProviderName = "Nager.Date", + SourceUrl = "https://date.nager.at/api/v3/PublicHolidays/2026/CA", + Title = "Canada Public Holidays", + TrustLevel = "Verified" + }, + new + { + Id = new Guid("10000000-0000-0000-0000-000000000003"), + Category = "marketing-moment", + CreatedAt = new DateTimeOffset(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + DefaultColor = "#9B51E0", + Description = "Common retail, awareness, and social planning moments.", + Language = "en", + ProviderName = "Socialize", + SourceUrl = "https://example.com/socialize/marketing-moments.ics", + Title = "Common Marketing Moments", + TrustLevel = "Maintained" + }); + }); + + modelBuilder.Entity("Socialize.Api.Modules.CalendarIntegrations.Data.CalendarEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CalendarSourceId") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("EndDate") + .HasColumnType("date"); + + b.Property("EndLocalDateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("EndUtc") + .HasColumnType("timestamp with time zone"); + + b.Property("ImportedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsAllDay") + .HasColumnType("boolean"); + + b.Property("IsFloatingTime") + .HasColumnType("boolean"); + + b.Property("Location") + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("RecurrenceId") + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("SourceEventUid") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("SourceLastModifiedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("SourceUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("StartDate") + .HasColumnType("date"); + + b.Property("StartLocalDateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("StartUtc") + .HasColumnType("timestamp with time zone"); + + b.Property("TimeZoneId") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.HasKey("Id"); + + b.HasIndex("CalendarSourceId"); + + b.HasIndex("CalendarSourceId", "SourceEventUid", "StartDate") + .IsUnique(); + + b.ToTable("CalendarEvents", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.CalendarIntegrations.Data.CalendarSource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CatalogSourceReference") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Category") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Color") + .IsRequired() + .HasMaxLength(16) + .HasColumnType("character varying(16)"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("DisplayTitle") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("InheritanceMode") + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("IsEnabled") + .HasColumnType("boolean"); + + b.Property("LastAttemptedSyncAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastSuccessfulSyncAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastSyncError") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Scope") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("SourceUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("WorkspaceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("Scope"); + + b.HasIndex("UserId"); + + b.HasIndex("WorkspaceId"); + + b.ToTable("CalendarSources", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.CalendarIntegrations.Data.UserCalendarExportFeed", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("RevokedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Token") + .HasMaxLength(96) + .HasColumnType("character varying(96)"); + + b.Property("TokenHash") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TokenHash") + .IsUnique(); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("UserCalendarExportFeeds", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Campaigns.Data.Campaign", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClientId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("Description") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("EndDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Notes") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("StartDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("WorkspaceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.HasIndex("WorkspaceId"); + + b.HasIndex("ClientId", "Name") + .IsUnique(); + + b.ToTable("Campaigns", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Channels.Data.Channel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("ExternalUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("Handle") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Network") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("WorkspaceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("WorkspaceId"); + + b.HasIndex("WorkspaceId", "Network", "Name") + .IsUnique(); + + b.ToTable("Channels", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Clients.Data.Client", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PortraitUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("PrimaryContactEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PrimaryContactName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PrimaryContactPortraitUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("WorkspaceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("WorkspaceId"); + + b.HasIndex("WorkspaceId", "Name") + .IsUnique(); + + b.ToTable("Clients", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Comments.Data.Comment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AttachmentBlobContainerName") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("AttachmentBlobName") + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("AttachmentBlobUrl") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("AttachmentContentType") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("AttachmentFileName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("AttachmentSizeBytes") + .HasColumnType("bigint"); + + b.Property("AuthorDisplayName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("AuthorEmail") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("AuthorUserId") + .HasColumnType("uuid"); + + b.Property("Body") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("ContentItemId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("ParentCommentId") + .HasColumnType("uuid"); + + b.Property("WorkspaceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ContentItemId"); + + b.HasIndex("ParentCommentId"); + + b.HasIndex("WorkspaceId"); + + b.ToTable("Comments", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.ContentItems.Data.ContentItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CampaignId") + .HasColumnType("uuid"); + + b.Property("ClientId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("CurrentRevisionLabel") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("CurrentRevisionNumber") + .HasColumnType("integer"); + + b.Property("DueDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Hashtags") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("PublicationMessage") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("PublicationTargets") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("WorkspaceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("CampaignId"); + + b.HasIndex("ClientId"); + + b.HasIndex("WorkspaceId"); + + b.ToTable("ContentItems", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.ContentItems.Data.ContentItemActivityEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ActorEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ActorUserId") + .HasColumnType("uuid"); + + b.Property("ContentItemId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("EntityId") + .HasColumnType("uuid"); + + b.Property("EntityType") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("EventType") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("MetadataJson") + .HasColumnType("jsonb"); + + b.Property("Summary") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("WorkspaceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ContentItemId"); + + b.HasIndex("WorkspaceId"); + + b.HasIndex("ContentItemId", "CreatedAt"); + + b.ToTable("ContentItemActivityEntries", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.ContentItems.Data.ContentItemRevision", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ChangeSummary") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("ContentItemId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("CreatedByUserId") + .HasColumnType("uuid"); + + b.Property("Hashtags") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("PublicationMessage") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("PublicationTargets") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("RevisionLabel") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("RevisionNumber") + .HasColumnType("integer"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("ContentItemId"); + + b.HasIndex("ContentItemId", "RevisionNumber") + .IsUnique(); + + b.ToTable("ContentItemRevisions", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Feedback.Data.FeedbackActivityEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ActivityType") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("ActorDisplayName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ActorEmail") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ActorUserId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("FeedbackReportId") + .HasColumnType("uuid"); + + b.Property("FromValue") + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("Note") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("ToValue") + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.HasKey("Id"); + + b.HasIndex("ActorUserId"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("FeedbackReportId"); + + b.ToTable("FeedbackActivityEntries", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Feedback.Data.FeedbackComment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AuthorDisplayName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("AuthorEmail") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("AuthorRole") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("AuthorUserId") + .HasColumnType("uuid"); + + b.Property("Body") + .IsRequired() + .HasMaxLength(8000) + .HasColumnType("character varying(8000)"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("FeedbackReportId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AuthorUserId"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("FeedbackReportId"); + + b.ToTable("FeedbackComments", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Feedback.Data.FeedbackReport", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppVersion") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("BrowserUserAgent") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("CampaignId") + .HasColumnType("uuid"); + + b.Property("CampaignName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("CancellationReason") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("CancelledAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CancelledByUserId") + .HasColumnType("uuid"); + + b.Property("ClientId") + .HasColumnType("uuid"); + + b.Property("ClientName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ContentItemId") + .HasColumnType("uuid"); + + b.Property("ContentItemTitle") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(8000) + .HasColumnType("character varying(8000)"); + + b.Property("LastActivityAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ReporterDisplayName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ReporterEmail") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ReporterUserId") + .HasColumnType("uuid"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("SubmittedPath") + .IsRequired() + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("ViewportHeight") + .HasColumnType("integer"); + + b.Property("ViewportWidth") + .HasColumnType("integer"); + + b.Property("WorkspaceId") + .HasColumnType("uuid"); + + b.Property("WorkspaceName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("CampaignId"); + + b.HasIndex("ClientId"); + + b.HasIndex("ContentItemId"); + + b.HasIndex("LastActivityAt"); + + b.HasIndex("ReporterUserId"); + + b.HasIndex("Status"); + + b.HasIndex("Type"); + + b.HasIndex("WorkspaceId"); + + b.ToTable("FeedbackReports", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Feedback.Data.FeedbackScreenshot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BlobContainerName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("BlobName") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("ContentType") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("FeedbackReportId") + .HasColumnType("uuid"); + + b.Property("FileName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("FeedbackReportId") + .IsUnique(); + + b.ToTable("FeedbackScreenshots", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Feedback.Data.FeedbackTag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("FeedbackReportId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("NormalizedName") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName"); + + b.HasIndex("FeedbackReportId", "NormalizedName") + .IsUnique(); + + b.ToTable("FeedbackTags", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Identity.Data.Role", 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("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Identity.Data.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("Address") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Alias") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("BirthDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("FacebookId") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Firstname") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("GoogleId") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("LastAuthenticatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Lastname") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .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("PortraitUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("RefreshToken") + .HasMaxLength(44) + .HasColumnType("character varying(44)"); + + b.Property("RefreshTokenExpiryTime") + .HasColumnType("timestamp with time zone"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Notifications.Data.NotificationEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ContentItemId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("EntityId") + .HasColumnType("uuid"); + + b.Property("EntityType") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("EventType") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("MetadataJson") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("ReadAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RecipientEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("RecipientUserId") + .HasColumnType("uuid"); + + b.Property("WorkspaceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ContentItemId"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("RecipientUserId"); + + b.HasIndex("WorkspaceId"); + + b.ToTable("NotificationEvents", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Organizations.Data.Organization", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("LogoUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("MembershipTierId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasDefaultValue(new Guid("20000000-0000-0000-0000-000000000001")); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("OwnerUserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("MembershipTierId"); + + b.HasIndex("OwnerUserId"); + + b.ToTable("Organizations", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Organizations.Data.OrganizationMembership", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.HasIndex("OrganizationId", "UserId") + .IsUnique(); + + b.ToTable("OrganizationMemberships", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Organizations.Data.OrganizationMembershipTier", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ActiveContentLimit") + .HasColumnType("integer"); + + b.Property("ExternalReviewerLimit") + .HasColumnType("integer"); + + b.Property("IsCustom") + .HasColumnType("boolean"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("MemberLimit") + .HasColumnType("integer"); + + b.Property("MonthlyPriceCents") + .HasColumnType("integer"); + + b.Property("SortOrder") + .HasColumnType("integer"); + + b.Property("WorkspaceLimit") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Key") + .IsUnique(); + + b.HasIndex("SortOrder"); + + b.ToTable("OrganizationMembershipTiers", (string)null); + + b.HasData( + new + { + Id = new Guid("20000000-0000-0000-0000-000000000001"), + ActiveContentLimit = 3, + ExternalReviewerLimit = 1, + IsCustom = false, + Key = "free", + MemberLimit = 2, + MonthlyPriceCents = 0, + SortOrder = 10, + WorkspaceLimit = 1 + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000002"), + ActiveContentLimit = 25, + ExternalReviewerLimit = 10, + IsCustom = false, + Key = "freelance", + MemberLimit = 5, + MonthlyPriceCents = 1900, + SortOrder = 20, + WorkspaceLimit = 3 + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000003"), + ActiveContentLimit = 250, + IsCustom = false, + Key = "agency", + MemberLimit = 25, + MonthlyPriceCents = 7900, + SortOrder = 30, + WorkspaceLimit = 15 + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000004"), + IsCustom = true, + Key = "enterprise", + SortOrder = 40 + }); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Organizations.Data.OrganizationMembershipTierTranslation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Culture") + .IsRequired() + .HasMaxLength(16) + .HasColumnType("character varying(16)"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("MembershipTierId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.HasKey("Id"); + + b.HasIndex("MembershipTierId", "Culture") + .IsUnique(); + + b.ToTable("OrganizationMembershipTierTranslations", (string)null); + + b.HasData( + new + { + Id = new Guid("20000000-0000-0001-0000-000000000001"), + Culture = "en", + Description = "For trying Socialize on one real approval workflow.", + MembershipTierId = new Guid("20000000-0000-0000-0000-000000000001"), + Name = "Free" + }, + new + { + Id = new Guid("20000000-0000-0001-0000-000000000002"), + Culture = "fr", + Description = "Pour essayer Socialize sur un vrai workflow d'approbation.", + MembershipTierId = new Guid("20000000-0000-0000-0000-000000000001"), + Name = "Free" + }, + new + { + Id = new Guid("20000000-0000-0001-0000-000000000003"), + Culture = "en", + Description = "For solo operators managing recurring client reviews.", + MembershipTierId = new Guid("20000000-0000-0000-0000-000000000002"), + Name = "Freelance" + }, + new + { + Id = new Guid("20000000-0000-0001-0000-000000000004"), + Culture = "fr", + Description = "Pour les independants qui gerent des revisions client recurrentes.", + MembershipTierId = new Guid("20000000-0000-0000-0000-000000000002"), + Name = "Freelance" + }, + new + { + Id = new Guid("20000000-0000-0001-0000-000000000005"), + Culture = "en", + Description = "For agencies that need repeatable client approval operations.", + MembershipTierId = new Guid("20000000-0000-0000-0000-000000000003"), + Name = "Agency" + }, + new + { + Id = new Guid("20000000-0000-0001-0000-000000000006"), + Culture = "fr", + Description = "Pour les agences qui veulent des operations d'approbation client repetables.", + MembershipTierId = new Guid("20000000-0000-0000-0000-000000000003"), + Name = "Agency" + }, + new + { + Id = new Guid("20000000-0000-0001-0000-000000000007"), + Culture = "en", + Description = "For larger organizations with governance and access needs.", + MembershipTierId = new Guid("20000000-0000-0000-0000-000000000004"), + Name = "Enterprise" + }, + new + { + Id = new Guid("20000000-0000-0001-0000-000000000008"), + Culture = "fr", + Description = "Pour les grandes organisations avec des besoins de gouvernance et d'acces.", + MembershipTierId = new Guid("20000000-0000-0000-0000-000000000004"), + Name = "Enterprise" + }); + }); + + modelBuilder.Entity("Socialize.Api.Modules.ReleaseCommunications.Data.ReleaseCommit", b => + { + b.Property("Sha") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("AuthorEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("AuthorName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("AuthoredAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CommittedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CommunicationStatus") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("DeploymentLabel") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("ExternalUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("ImportedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("ReleaseUpdateId") + .HasColumnType("uuid"); + + b.Property("ShortSha") + .IsRequired() + .HasMaxLength(16) + .HasColumnType("character varying(16)"); + + b.Property("SourceBranch") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Subject") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Sha"); + + b.HasIndex("CommittedAt"); + + b.HasIndex("CommunicationStatus"); + + b.HasIndex("ReleaseUpdateId"); + + b.ToTable("ReleaseCommits", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.ReleaseCommunications.Data.ReleaseUpdate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ArchivedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("CreatedByUserId") + .HasColumnType("uuid"); + + b.Property("ManualEmailRecipientCount") + .HasColumnType("integer"); + + b.Property("ManualEmailSentAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ManualEmailSentByUserId") + .HasColumnType("uuid"); + + b.Property("PublishedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("Summary") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(160) + .HasColumnType("character varying(160)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("CreatedByUserId"); + + b.HasIndex("PublishedAt"); + + b.HasIndex("Status"); + + b.ToTable("ReleaseUpdates", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.ReleaseCommunications.Data.ReleaseUpdateEmailDigestReceipt", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("SentAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("UpdateCount") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("SentAt"); + + b.HasIndex("UserId"); + + b.ToTable("ReleaseUpdateEmailDigestReceipts", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.ReleaseCommunications.Data.ReleaseUpdateReadReceipt", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ReadAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("ReleaseUpdateId") + .HasColumnType("uuid"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("ReleaseUpdateId", "UserId") + .IsUnique(); + + b.ToTable("ReleaseUpdateReadReceipts", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Workspaces.Data.Workspace", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApprovalMode") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasDefaultValue("Required"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("LockContentAfterApproval") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("LogoUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("OwnerUserId") + .HasColumnType("uuid"); + + b.Property("SchedulePostsAutomaticallyOnApproval") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("SendAutomaticApprovalReminders") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("TimeZone") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("OwnerUserId"); + + b.ToTable("Workspaces", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Workspaces.Data.WorkspaceInvite", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("InvitedByUserId") + .HasColumnType("uuid"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("WorkspaceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("WorkspaceId"); + + b.HasIndex("WorkspaceId", "Email", "Status"); + + b.ToTable("WorkspaceInvites", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Socialize.Api.Modules.Identity.Data.Role", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Socialize.Api.Modules.Identity.Data.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Socialize.Api.Modules.Identity.Data.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Socialize.Api.Modules.Identity.Data.Role", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Socialize.Api.Modules.Identity.Data.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Socialize.Api.Modules.Identity.Data.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Approvals.Data.ApprovalDecision", b => + { + b.HasOne("Socialize.Api.Modules.Approvals.Data.ApprovalRequest", null) + .WithMany() + .HasForeignKey("ApprovalRequestId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Approvals.Data.ApprovalRequest", b => + { + b.HasOne("Socialize.Api.Modules.ContentItems.Data.ContentItem", null) + .WithMany() + .HasForeignKey("ContentItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Socialize.Api.Modules.Approvals.Data.ApprovalWorkflowInstance", null) + .WithMany() + .HasForeignKey("WorkflowInstanceId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Socialize.Api.Modules.Workspaces.Data.Workspace", null) + .WithMany() + .HasForeignKey("WorkspaceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Approvals.Data.ApprovalWorkflowInstance", b => + { + b.HasOne("Socialize.Api.Modules.ContentItems.Data.ContentItem", null) + .WithMany() + .HasForeignKey("ContentItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Socialize.Api.Modules.Workspaces.Data.Workspace", null) + .WithMany() + .HasForeignKey("WorkspaceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Approvals.Data.WorkspaceApprovalStepConfiguration", b => + { + b.HasOne("Socialize.Api.Modules.Workspaces.Data.Workspace", null) + .WithMany() + .HasForeignKey("WorkspaceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Assets.Data.Asset", b => + { + b.HasOne("Socialize.Api.Modules.ContentItems.Data.ContentItem", null) + .WithMany() + .HasForeignKey("ContentItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Socialize.Api.Modules.Workspaces.Data.Workspace", null) + .WithMany() + .HasForeignKey("WorkspaceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Assets.Data.AssetRevision", b => + { + b.HasOne("Socialize.Api.Modules.Assets.Data.Asset", null) + .WithMany() + .HasForeignKey("AssetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.CalendarIntegrations.Data.CalendarEvent", b => + { + b.HasOne("Socialize.Api.Modules.CalendarIntegrations.Data.CalendarSource", null) + .WithMany() + .HasForeignKey("CalendarSourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Campaigns.Data.Campaign", b => + { + b.HasOne("Socialize.Api.Modules.Clients.Data.Client", null) + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Socialize.Api.Modules.Workspaces.Data.Workspace", null) + .WithMany() + .HasForeignKey("WorkspaceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Channels.Data.Channel", b => + { + b.HasOne("Socialize.Api.Modules.Workspaces.Data.Workspace", null) + .WithMany() + .HasForeignKey("WorkspaceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Clients.Data.Client", b => + { + b.HasOne("Socialize.Api.Modules.Workspaces.Data.Workspace", null) + .WithMany() + .HasForeignKey("WorkspaceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Comments.Data.Comment", b => + { + b.HasOne("Socialize.Api.Modules.ContentItems.Data.ContentItem", null) + .WithMany() + .HasForeignKey("ContentItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Socialize.Api.Modules.Comments.Data.Comment", null) + .WithMany() + .HasForeignKey("ParentCommentId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Socialize.Api.Modules.Workspaces.Data.Workspace", null) + .WithMany() + .HasForeignKey("WorkspaceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.ContentItems.Data.ContentItem", b => + { + b.HasOne("Socialize.Api.Modules.Campaigns.Data.Campaign", null) + .WithMany() + .HasForeignKey("CampaignId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Socialize.Api.Modules.Clients.Data.Client", null) + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Socialize.Api.Modules.Workspaces.Data.Workspace", null) + .WithMany() + .HasForeignKey("WorkspaceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.ContentItems.Data.ContentItemActivityEntry", b => + { + b.HasOne("Socialize.Api.Modules.ContentItems.Data.ContentItem", null) + .WithMany() + .HasForeignKey("ContentItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Socialize.Api.Modules.Workspaces.Data.Workspace", null) + .WithMany() + .HasForeignKey("WorkspaceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.ContentItems.Data.ContentItemRevision", b => + { + b.HasOne("Socialize.Api.Modules.ContentItems.Data.ContentItem", null) + .WithMany() + .HasForeignKey("ContentItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Feedback.Data.FeedbackActivityEntry", b => + { + b.HasOne("Socialize.Api.Modules.Feedback.Data.FeedbackReport", "FeedbackReport") + .WithMany("ActivityEntries") + .HasForeignKey("FeedbackReportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("FeedbackReport"); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Feedback.Data.FeedbackComment", b => + { + b.HasOne("Socialize.Api.Modules.Feedback.Data.FeedbackReport", "FeedbackReport") + .WithMany("Comments") + .HasForeignKey("FeedbackReportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("FeedbackReport"); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Feedback.Data.FeedbackReport", b => + { + b.HasOne("Socialize.Api.Modules.Campaigns.Data.Campaign", null) + .WithMany() + .HasForeignKey("CampaignId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Socialize.Api.Modules.Clients.Data.Client", null) + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Socialize.Api.Modules.ContentItems.Data.ContentItem", null) + .WithMany() + .HasForeignKey("ContentItemId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Socialize.Api.Modules.Workspaces.Data.Workspace", null) + .WithMany() + .HasForeignKey("WorkspaceId") + .OnDelete(DeleteBehavior.SetNull); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Feedback.Data.FeedbackScreenshot", b => + { + b.HasOne("Socialize.Api.Modules.Feedback.Data.FeedbackReport", "FeedbackReport") + .WithOne("Screenshot") + .HasForeignKey("Socialize.Api.Modules.Feedback.Data.FeedbackScreenshot", "FeedbackReportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("FeedbackReport"); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Feedback.Data.FeedbackTag", b => + { + b.HasOne("Socialize.Api.Modules.Feedback.Data.FeedbackReport", "FeedbackReport") + .WithMany("Tags") + .HasForeignKey("FeedbackReportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("FeedbackReport"); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Notifications.Data.NotificationEvent", b => + { + b.HasOne("Socialize.Api.Modules.ContentItems.Data.ContentItem", null) + .WithMany() + .HasForeignKey("ContentItemId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Socialize.Api.Modules.Workspaces.Data.Workspace", null) + .WithMany() + .HasForeignKey("WorkspaceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Organizations.Data.Organization", b => + { + b.HasOne("Socialize.Api.Modules.Organizations.Data.OrganizationMembershipTier", null) + .WithMany() + .HasForeignKey("MembershipTierId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Organizations.Data.OrganizationMembership", b => + { + b.HasOne("Socialize.Api.Modules.Organizations.Data.Organization", null) + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Organizations.Data.OrganizationMembershipTierTranslation", b => + { + b.HasOne("Socialize.Api.Modules.Organizations.Data.OrganizationMembershipTier", null) + .WithMany() + .HasForeignKey("MembershipTierId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.ReleaseCommunications.Data.ReleaseCommit", b => + { + b.HasOne("Socialize.Api.Modules.ReleaseCommunications.Data.ReleaseUpdate", "ReleaseUpdate") + .WithMany() + .HasForeignKey("ReleaseUpdateId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ReleaseUpdate"); + }); + + modelBuilder.Entity("Socialize.Api.Modules.ReleaseCommunications.Data.ReleaseUpdateReadReceipt", b => + { + b.HasOne("Socialize.Api.Modules.ReleaseCommunications.Data.ReleaseUpdate", "ReleaseUpdate") + .WithMany("ReadReceipts") + .HasForeignKey("ReleaseUpdateId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ReleaseUpdate"); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Workspaces.Data.Workspace", b => + { + b.HasOne("Socialize.Api.Modules.Organizations.Data.Organization", null) + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Workspaces.Data.WorkspaceInvite", b => + { + b.HasOne("Socialize.Api.Modules.Workspaces.Data.Workspace", null) + .WithMany() + .HasForeignKey("WorkspaceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Feedback.Data.FeedbackReport", b => + { + b.Navigation("ActivityEntries"); + + b.Navigation("Comments"); + + b.Navigation("Screenshot"); + + b.Navigation("Tags"); + }); + + modelBuilder.Entity("Socialize.Api.Modules.ReleaseCommunications.Data.ReleaseUpdate", b => + { + b.Navigation("ReadReceipts"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/backend/src/Socialize.Api/Migrations/20260508030349_SimplifyReleaseUpdates.cs b/backend/src/Socialize.Api/Migrations/20260508030349_SimplifyReleaseUpdates.cs new file mode 100644 index 00000000..ab4195fc --- /dev/null +++ b/backend/src/Socialize.Api/Migrations/20260508030349_SimplifyReleaseUpdates.cs @@ -0,0 +1,118 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Socialize.Api.Migrations +{ + /// + internal partial class SimplifyReleaseUpdates : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_ReleaseUpdates_Audience", + table: "ReleaseUpdates"); + + migrationBuilder.DropColumn( + name: "Audience", + table: "ReleaseUpdates"); + + migrationBuilder.DropColumn( + name: "Body", + table: "ReleaseUpdates"); + + migrationBuilder.DropColumn( + name: "BuildVersion", + table: "ReleaseUpdates"); + + migrationBuilder.DropColumn( + name: "Category", + table: "ReleaseUpdates"); + + migrationBuilder.DropColumn( + name: "CommitRange", + table: "ReleaseUpdates"); + + migrationBuilder.DropColumn( + name: "DeploymentLabel", + table: "ReleaseUpdates"); + + migrationBuilder.DropColumn( + name: "Importance", + table: "ReleaseUpdates"); + + migrationBuilder.DropColumn( + name: "ManualEmailAudience", + table: "ReleaseUpdates"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Audience", + table: "ReleaseUpdates", + type: "character varying(32)", + maxLength: 32, + nullable: false, + defaultValue: ""); + + migrationBuilder.AddColumn( + name: "Body", + table: "ReleaseUpdates", + type: "character varying(8000)", + maxLength: 8000, + nullable: true); + + migrationBuilder.AddColumn( + name: "BuildVersion", + table: "ReleaseUpdates", + type: "character varying(128)", + maxLength: 128, + nullable: true); + + migrationBuilder.AddColumn( + name: "Category", + table: "ReleaseUpdates", + type: "character varying(32)", + maxLength: 32, + nullable: false, + defaultValue: ""); + + migrationBuilder.AddColumn( + name: "CommitRange", + table: "ReleaseUpdates", + type: "character varying(256)", + maxLength: 256, + nullable: true); + + migrationBuilder.AddColumn( + name: "DeploymentLabel", + table: "ReleaseUpdates", + type: "character varying(128)", + maxLength: 128, + nullable: true); + + migrationBuilder.AddColumn( + name: "Importance", + table: "ReleaseUpdates", + type: "character varying(32)", + maxLength: 32, + nullable: false, + defaultValue: ""); + + migrationBuilder.AddColumn( + name: "ManualEmailAudience", + table: "ReleaseUpdates", + type: "character varying(64)", + maxLength: 64, + nullable: true); + + migrationBuilder.CreateIndex( + name: "IX_ReleaseUpdates_Audience", + table: "ReleaseUpdates", + column: "Audience"); + } + } +} diff --git a/backend/src/Socialize.Api/Migrations/20260508031114_AddFrenchReleaseUpdateFields.Designer.cs b/backend/src/Socialize.Api/Migrations/20260508031114_AddFrenchReleaseUpdateFields.Designer.cs new file mode 100644 index 00000000..ab003ede --- /dev/null +++ b/backend/src/Socialize.Api/Migrations/20260508031114_AddFrenchReleaseUpdateFields.Designer.cs @@ -0,0 +1,2601 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Socialize.Api.Data; + +#nullable disable + +namespace Socialize.Api.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20260508031114_AddFrenchReleaseUpdateFields")] + partial class AddFrenchReleaseUpdateFields + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + 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("AspNetRoleClaims", (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("AspNetUserClaims", (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("AspNetUserLogins", (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("AspNetUserRoles", (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("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Approvals.Data.ApprovalDecision", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApprovalRequestId") + .HasColumnType("uuid"); + + b.Property("Comment") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("DecidedByEmail") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("DecidedByName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("DecidedByUserId") + .HasColumnType("uuid"); + + b.Property("Decision") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.HasKey("Id"); + + b.HasIndex("ApprovalRequestId"); + + b.ToTable("ApprovalDecisions", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Approvals.Data.ApprovalRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccessToken") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ContentItemId") + .HasColumnType("uuid"); + + b.Property("DueAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RequestedByUserId") + .HasColumnType("uuid"); + + b.Property("ReviewerEmail") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ReviewerName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("SentAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("Stage") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("State") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("WorkflowInstanceId") + .HasColumnType("uuid"); + + b.Property("WorkflowStepRequiredApproverCount") + .HasColumnType("integer"); + + b.Property("WorkflowStepSortOrder") + .HasColumnType("integer"); + + b.Property("WorkflowStepTargetType") + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("WorkflowStepTargetValue") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("WorkspaceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ContentItemId"); + + b.HasIndex("ReviewerEmail"); + + b.HasIndex("WorkflowInstanceId"); + + b.HasIndex("WorkspaceId"); + + b.ToTable("ApprovalRequests", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Approvals.Data.ApprovalWorkflowInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApprovalMode") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ContentItemId") + .HasColumnType("uuid"); + + b.Property("StartedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("State") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("WorkspaceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ContentItemId"); + + b.HasIndex("WorkspaceId"); + + b.HasIndex("ContentItemId", "State") + .IsUnique() + .HasFilter("\"State\" = 'Pending'"); + + b.ToTable("ApprovalWorkflowInstances", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Approvals.Data.WorkspaceApprovalStepConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("RequiredApproverCount") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(1); + + b.Property("SortOrder") + .HasColumnType("integer"); + + b.Property("TargetType") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("TargetValue") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("WorkspaceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("WorkspaceId"); + + b.HasIndex("WorkspaceId", "SortOrder") + .IsUnique(); + + b.ToTable("WorkspaceApprovalStepConfigurations", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Assets.Data.Asset", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AssetType") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("ContentItemId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("CurrentRevisionNumber") + .HasColumnType("integer"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("GoogleDriveFileId") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("GoogleDriveLink") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("PreviewUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("SourceType") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("WorkspaceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ContentItemId"); + + b.HasIndex("WorkspaceId"); + + b.ToTable("Assets", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Assets.Data.AssetRevision", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AssetId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("CreatedByUserId") + .HasColumnType("uuid"); + + b.Property("Notes") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("PreviewUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("RevisionNumber") + .HasColumnType("integer"); + + b.Property("SourceReference") + .IsRequired() + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.HasKey("Id"); + + b.HasIndex("AssetId"); + + b.HasIndex("AssetId", "RevisionNumber") + .IsUnique(); + + b.ToTable("AssetRevisions", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.CalendarIntegrations.Data.CalendarCatalogEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Category") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Country") + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("CultureOrReligion") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("DefaultColor") + .IsRequired() + .HasMaxLength(16) + .HasColumnType("character varying(16)"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("Language") + .IsRequired() + .HasMaxLength(16) + .HasColumnType("character varying(16)"); + + b.Property("ProviderName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("Region") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("SourceUrl") + .IsRequired() + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("TrustLevel") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.HasKey("Id"); + + b.HasIndex("Category"); + + b.HasIndex("Country"); + + b.HasIndex("ProviderName"); + + b.ToTable("CalendarCatalogEntries", (string)null); + + b.HasData( + new + { + Id = new Guid("10000000-0000-0000-0000-000000000001"), + Category = "public-holiday", + Country = "US", + CreatedAt = new DateTimeOffset(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + DefaultColor = "#2F80ED", + Description = "Federal public holiday calendar for the United States.", + Language = "en", + ProviderName = "Nager.Date", + SourceUrl = "https://date.nager.at/api/v3/PublicHolidays/2026/US", + Title = "United States Public Holidays", + TrustLevel = "Verified" + }, + new + { + Id = new Guid("10000000-0000-0000-0000-000000000002"), + Category = "public-holiday", + Country = "CA", + CreatedAt = new DateTimeOffset(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + DefaultColor = "#2F80ED", + Description = "Public holiday calendar for Canada.", + Language = "en", + ProviderName = "Nager.Date", + SourceUrl = "https://date.nager.at/api/v3/PublicHolidays/2026/CA", + Title = "Canada Public Holidays", + TrustLevel = "Verified" + }, + new + { + Id = new Guid("10000000-0000-0000-0000-000000000003"), + Category = "marketing-moment", + CreatedAt = new DateTimeOffset(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + DefaultColor = "#9B51E0", + Description = "Common retail, awareness, and social planning moments.", + Language = "en", + ProviderName = "Socialize", + SourceUrl = "https://example.com/socialize/marketing-moments.ics", + Title = "Common Marketing Moments", + TrustLevel = "Maintained" + }); + }); + + modelBuilder.Entity("Socialize.Api.Modules.CalendarIntegrations.Data.CalendarEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CalendarSourceId") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("EndDate") + .HasColumnType("date"); + + b.Property("EndLocalDateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("EndUtc") + .HasColumnType("timestamp with time zone"); + + b.Property("ImportedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsAllDay") + .HasColumnType("boolean"); + + b.Property("IsFloatingTime") + .HasColumnType("boolean"); + + b.Property("Location") + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("RecurrenceId") + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("SourceEventUid") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("SourceLastModifiedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("SourceUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("StartDate") + .HasColumnType("date"); + + b.Property("StartLocalDateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("StartUtc") + .HasColumnType("timestamp with time zone"); + + b.Property("TimeZoneId") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.HasKey("Id"); + + b.HasIndex("CalendarSourceId"); + + b.HasIndex("CalendarSourceId", "SourceEventUid", "StartDate") + .IsUnique(); + + b.ToTable("CalendarEvents", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.CalendarIntegrations.Data.CalendarSource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CatalogSourceReference") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Category") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Color") + .IsRequired() + .HasMaxLength(16) + .HasColumnType("character varying(16)"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("DisplayTitle") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("InheritanceMode") + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("IsEnabled") + .HasColumnType("boolean"); + + b.Property("LastAttemptedSyncAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastSuccessfulSyncAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastSyncError") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Scope") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("SourceUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("WorkspaceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("Scope"); + + b.HasIndex("UserId"); + + b.HasIndex("WorkspaceId"); + + b.ToTable("CalendarSources", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.CalendarIntegrations.Data.UserCalendarExportFeed", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("RevokedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Token") + .HasMaxLength(96) + .HasColumnType("character varying(96)"); + + b.Property("TokenHash") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TokenHash") + .IsUnique(); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("UserCalendarExportFeeds", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Campaigns.Data.Campaign", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClientId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("Description") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("EndDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Notes") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("StartDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("WorkspaceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.HasIndex("WorkspaceId"); + + b.HasIndex("ClientId", "Name") + .IsUnique(); + + b.ToTable("Campaigns", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Channels.Data.Channel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("ExternalUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("Handle") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Network") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("WorkspaceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("WorkspaceId"); + + b.HasIndex("WorkspaceId", "Network", "Name") + .IsUnique(); + + b.ToTable("Channels", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Clients.Data.Client", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PortraitUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("PrimaryContactEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PrimaryContactName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PrimaryContactPortraitUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("WorkspaceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("WorkspaceId"); + + b.HasIndex("WorkspaceId", "Name") + .IsUnique(); + + b.ToTable("Clients", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Comments.Data.Comment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AttachmentBlobContainerName") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("AttachmentBlobName") + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("AttachmentBlobUrl") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("AttachmentContentType") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("AttachmentFileName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("AttachmentSizeBytes") + .HasColumnType("bigint"); + + b.Property("AuthorDisplayName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("AuthorEmail") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("AuthorUserId") + .HasColumnType("uuid"); + + b.Property("Body") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("ContentItemId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("ParentCommentId") + .HasColumnType("uuid"); + + b.Property("WorkspaceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ContentItemId"); + + b.HasIndex("ParentCommentId"); + + b.HasIndex("WorkspaceId"); + + b.ToTable("Comments", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.ContentItems.Data.ContentItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CampaignId") + .HasColumnType("uuid"); + + b.Property("ClientId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("CurrentRevisionLabel") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("CurrentRevisionNumber") + .HasColumnType("integer"); + + b.Property("DueDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Hashtags") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("PublicationMessage") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("PublicationTargets") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("WorkspaceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("CampaignId"); + + b.HasIndex("ClientId"); + + b.HasIndex("WorkspaceId"); + + b.ToTable("ContentItems", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.ContentItems.Data.ContentItemActivityEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ActorEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ActorUserId") + .HasColumnType("uuid"); + + b.Property("ContentItemId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("EntityId") + .HasColumnType("uuid"); + + b.Property("EntityType") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("EventType") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("MetadataJson") + .HasColumnType("jsonb"); + + b.Property("Summary") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("WorkspaceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ContentItemId"); + + b.HasIndex("WorkspaceId"); + + b.HasIndex("ContentItemId", "CreatedAt"); + + b.ToTable("ContentItemActivityEntries", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.ContentItems.Data.ContentItemRevision", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ChangeSummary") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("ContentItemId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("CreatedByUserId") + .HasColumnType("uuid"); + + b.Property("Hashtags") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("PublicationMessage") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("PublicationTargets") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("RevisionLabel") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("RevisionNumber") + .HasColumnType("integer"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("ContentItemId"); + + b.HasIndex("ContentItemId", "RevisionNumber") + .IsUnique(); + + b.ToTable("ContentItemRevisions", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Feedback.Data.FeedbackActivityEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ActivityType") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("ActorDisplayName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ActorEmail") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ActorUserId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("FeedbackReportId") + .HasColumnType("uuid"); + + b.Property("FromValue") + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("Note") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("ToValue") + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.HasKey("Id"); + + b.HasIndex("ActorUserId"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("FeedbackReportId"); + + b.ToTable("FeedbackActivityEntries", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Feedback.Data.FeedbackComment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AuthorDisplayName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("AuthorEmail") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("AuthorRole") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("AuthorUserId") + .HasColumnType("uuid"); + + b.Property("Body") + .IsRequired() + .HasMaxLength(8000) + .HasColumnType("character varying(8000)"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("FeedbackReportId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AuthorUserId"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("FeedbackReportId"); + + b.ToTable("FeedbackComments", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Feedback.Data.FeedbackReport", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppVersion") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("BrowserUserAgent") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("CampaignId") + .HasColumnType("uuid"); + + b.Property("CampaignName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("CancellationReason") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("CancelledAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CancelledByUserId") + .HasColumnType("uuid"); + + b.Property("ClientId") + .HasColumnType("uuid"); + + b.Property("ClientName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ContentItemId") + .HasColumnType("uuid"); + + b.Property("ContentItemTitle") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(8000) + .HasColumnType("character varying(8000)"); + + b.Property("LastActivityAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ReporterDisplayName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ReporterEmail") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ReporterUserId") + .HasColumnType("uuid"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("SubmittedPath") + .IsRequired() + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("ViewportHeight") + .HasColumnType("integer"); + + b.Property("ViewportWidth") + .HasColumnType("integer"); + + b.Property("WorkspaceId") + .HasColumnType("uuid"); + + b.Property("WorkspaceName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("CampaignId"); + + b.HasIndex("ClientId"); + + b.HasIndex("ContentItemId"); + + b.HasIndex("LastActivityAt"); + + b.HasIndex("ReporterUserId"); + + b.HasIndex("Status"); + + b.HasIndex("Type"); + + b.HasIndex("WorkspaceId"); + + b.ToTable("FeedbackReports", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Feedback.Data.FeedbackScreenshot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BlobContainerName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("BlobName") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("ContentType") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("FeedbackReportId") + .HasColumnType("uuid"); + + b.Property("FileName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("FeedbackReportId") + .IsUnique(); + + b.ToTable("FeedbackScreenshots", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Feedback.Data.FeedbackTag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("FeedbackReportId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("NormalizedName") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName"); + + b.HasIndex("FeedbackReportId", "NormalizedName") + .IsUnique(); + + b.ToTable("FeedbackTags", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Identity.Data.Role", 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("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Identity.Data.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("Address") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Alias") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("BirthDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("FacebookId") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Firstname") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("GoogleId") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("LastAuthenticatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Lastname") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .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("PortraitUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("RefreshToken") + .HasMaxLength(44) + .HasColumnType("character varying(44)"); + + b.Property("RefreshTokenExpiryTime") + .HasColumnType("timestamp with time zone"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Notifications.Data.NotificationEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ContentItemId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("EntityId") + .HasColumnType("uuid"); + + b.Property("EntityType") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("EventType") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("MetadataJson") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("ReadAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RecipientEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("RecipientUserId") + .HasColumnType("uuid"); + + b.Property("WorkspaceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ContentItemId"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("RecipientUserId"); + + b.HasIndex("WorkspaceId"); + + b.ToTable("NotificationEvents", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Organizations.Data.Organization", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("LogoUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("MembershipTierId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasDefaultValue(new Guid("20000000-0000-0000-0000-000000000001")); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("OwnerUserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("MembershipTierId"); + + b.HasIndex("OwnerUserId"); + + b.ToTable("Organizations", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Organizations.Data.OrganizationMembership", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.HasIndex("OrganizationId", "UserId") + .IsUnique(); + + b.ToTable("OrganizationMemberships", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Organizations.Data.OrganizationMembershipTier", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ActiveContentLimit") + .HasColumnType("integer"); + + b.Property("ExternalReviewerLimit") + .HasColumnType("integer"); + + b.Property("IsCustom") + .HasColumnType("boolean"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("MemberLimit") + .HasColumnType("integer"); + + b.Property("MonthlyPriceCents") + .HasColumnType("integer"); + + b.Property("SortOrder") + .HasColumnType("integer"); + + b.Property("WorkspaceLimit") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Key") + .IsUnique(); + + b.HasIndex("SortOrder"); + + b.ToTable("OrganizationMembershipTiers", (string)null); + + b.HasData( + new + { + Id = new Guid("20000000-0000-0000-0000-000000000001"), + ActiveContentLimit = 3, + ExternalReviewerLimit = 1, + IsCustom = false, + Key = "free", + MemberLimit = 2, + MonthlyPriceCents = 0, + SortOrder = 10, + WorkspaceLimit = 1 + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000002"), + ActiveContentLimit = 25, + ExternalReviewerLimit = 10, + IsCustom = false, + Key = "freelance", + MemberLimit = 5, + MonthlyPriceCents = 1900, + SortOrder = 20, + WorkspaceLimit = 3 + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000003"), + ActiveContentLimit = 250, + IsCustom = false, + Key = "agency", + MemberLimit = 25, + MonthlyPriceCents = 7900, + SortOrder = 30, + WorkspaceLimit = 15 + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000004"), + IsCustom = true, + Key = "enterprise", + SortOrder = 40 + }); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Organizations.Data.OrganizationMembershipTierTranslation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Culture") + .IsRequired() + .HasMaxLength(16) + .HasColumnType("character varying(16)"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("MembershipTierId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.HasKey("Id"); + + b.HasIndex("MembershipTierId", "Culture") + .IsUnique(); + + b.ToTable("OrganizationMembershipTierTranslations", (string)null); + + b.HasData( + new + { + Id = new Guid("20000000-0000-0001-0000-000000000001"), + Culture = "en", + Description = "For trying Socialize on one real approval workflow.", + MembershipTierId = new Guid("20000000-0000-0000-0000-000000000001"), + Name = "Free" + }, + new + { + Id = new Guid("20000000-0000-0001-0000-000000000002"), + Culture = "fr", + Description = "Pour essayer Socialize sur un vrai workflow d'approbation.", + MembershipTierId = new Guid("20000000-0000-0000-0000-000000000001"), + Name = "Free" + }, + new + { + Id = new Guid("20000000-0000-0001-0000-000000000003"), + Culture = "en", + Description = "For solo operators managing recurring client reviews.", + MembershipTierId = new Guid("20000000-0000-0000-0000-000000000002"), + Name = "Freelance" + }, + new + { + Id = new Guid("20000000-0000-0001-0000-000000000004"), + Culture = "fr", + Description = "Pour les independants qui gerent des revisions client recurrentes.", + MembershipTierId = new Guid("20000000-0000-0000-0000-000000000002"), + Name = "Freelance" + }, + new + { + Id = new Guid("20000000-0000-0001-0000-000000000005"), + Culture = "en", + Description = "For agencies that need repeatable client approval operations.", + MembershipTierId = new Guid("20000000-0000-0000-0000-000000000003"), + Name = "Agency" + }, + new + { + Id = new Guid("20000000-0000-0001-0000-000000000006"), + Culture = "fr", + Description = "Pour les agences qui veulent des operations d'approbation client repetables.", + MembershipTierId = new Guid("20000000-0000-0000-0000-000000000003"), + Name = "Agency" + }, + new + { + Id = new Guid("20000000-0000-0001-0000-000000000007"), + Culture = "en", + Description = "For larger organizations with governance and access needs.", + MembershipTierId = new Guid("20000000-0000-0000-0000-000000000004"), + Name = "Enterprise" + }, + new + { + Id = new Guid("20000000-0000-0001-0000-000000000008"), + Culture = "fr", + Description = "Pour les grandes organisations avec des besoins de gouvernance et d'acces.", + MembershipTierId = new Guid("20000000-0000-0000-0000-000000000004"), + Name = "Enterprise" + }); + }); + + modelBuilder.Entity("Socialize.Api.Modules.ReleaseCommunications.Data.ReleaseCommit", b => + { + b.Property("Sha") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("AuthorEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("AuthorName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("AuthoredAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CommittedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CommunicationStatus") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("DeploymentLabel") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("ExternalUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("ImportedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("ReleaseUpdateId") + .HasColumnType("uuid"); + + b.Property("ShortSha") + .IsRequired() + .HasMaxLength(16) + .HasColumnType("character varying(16)"); + + b.Property("SourceBranch") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Subject") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Sha"); + + b.HasIndex("CommittedAt"); + + b.HasIndex("CommunicationStatus"); + + b.HasIndex("ReleaseUpdateId"); + + b.ToTable("ReleaseCommits", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.ReleaseCommunications.Data.ReleaseUpdate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ArchivedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("CreatedByUserId") + .HasColumnType("uuid"); + + b.Property("ManualEmailRecipientCount") + .HasColumnType("integer"); + + b.Property("ManualEmailSentAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ManualEmailSentByUserId") + .HasColumnType("uuid"); + + b.Property("PublishedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("Summary") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("SummaryFr") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(160) + .HasColumnType("character varying(160)"); + + b.Property("TitleFr") + .IsRequired() + .HasMaxLength(160) + .HasColumnType("character varying(160)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("CreatedByUserId"); + + b.HasIndex("PublishedAt"); + + b.HasIndex("Status"); + + b.ToTable("ReleaseUpdates", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.ReleaseCommunications.Data.ReleaseUpdateEmailDigestReceipt", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("SentAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("UpdateCount") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("SentAt"); + + b.HasIndex("UserId"); + + b.ToTable("ReleaseUpdateEmailDigestReceipts", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.ReleaseCommunications.Data.ReleaseUpdateReadReceipt", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ReadAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("ReleaseUpdateId") + .HasColumnType("uuid"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("ReleaseUpdateId", "UserId") + .IsUnique(); + + b.ToTable("ReleaseUpdateReadReceipts", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Workspaces.Data.Workspace", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApprovalMode") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasDefaultValue("Required"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("LockContentAfterApproval") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("LogoUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("OwnerUserId") + .HasColumnType("uuid"); + + b.Property("SchedulePostsAutomaticallyOnApproval") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("SendAutomaticApprovalReminders") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("TimeZone") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("OwnerUserId"); + + b.ToTable("Workspaces", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Workspaces.Data.WorkspaceInvite", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("InvitedByUserId") + .HasColumnType("uuid"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("WorkspaceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("WorkspaceId"); + + b.HasIndex("WorkspaceId", "Email", "Status"); + + b.ToTable("WorkspaceInvites", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Socialize.Api.Modules.Identity.Data.Role", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Socialize.Api.Modules.Identity.Data.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Socialize.Api.Modules.Identity.Data.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Socialize.Api.Modules.Identity.Data.Role", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Socialize.Api.Modules.Identity.Data.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Socialize.Api.Modules.Identity.Data.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Approvals.Data.ApprovalDecision", b => + { + b.HasOne("Socialize.Api.Modules.Approvals.Data.ApprovalRequest", null) + .WithMany() + .HasForeignKey("ApprovalRequestId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Approvals.Data.ApprovalRequest", b => + { + b.HasOne("Socialize.Api.Modules.ContentItems.Data.ContentItem", null) + .WithMany() + .HasForeignKey("ContentItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Socialize.Api.Modules.Approvals.Data.ApprovalWorkflowInstance", null) + .WithMany() + .HasForeignKey("WorkflowInstanceId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Socialize.Api.Modules.Workspaces.Data.Workspace", null) + .WithMany() + .HasForeignKey("WorkspaceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Approvals.Data.ApprovalWorkflowInstance", b => + { + b.HasOne("Socialize.Api.Modules.ContentItems.Data.ContentItem", null) + .WithMany() + .HasForeignKey("ContentItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Socialize.Api.Modules.Workspaces.Data.Workspace", null) + .WithMany() + .HasForeignKey("WorkspaceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Approvals.Data.WorkspaceApprovalStepConfiguration", b => + { + b.HasOne("Socialize.Api.Modules.Workspaces.Data.Workspace", null) + .WithMany() + .HasForeignKey("WorkspaceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Assets.Data.Asset", b => + { + b.HasOne("Socialize.Api.Modules.ContentItems.Data.ContentItem", null) + .WithMany() + .HasForeignKey("ContentItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Socialize.Api.Modules.Workspaces.Data.Workspace", null) + .WithMany() + .HasForeignKey("WorkspaceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Assets.Data.AssetRevision", b => + { + b.HasOne("Socialize.Api.Modules.Assets.Data.Asset", null) + .WithMany() + .HasForeignKey("AssetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.CalendarIntegrations.Data.CalendarEvent", b => + { + b.HasOne("Socialize.Api.Modules.CalendarIntegrations.Data.CalendarSource", null) + .WithMany() + .HasForeignKey("CalendarSourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Campaigns.Data.Campaign", b => + { + b.HasOne("Socialize.Api.Modules.Clients.Data.Client", null) + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Socialize.Api.Modules.Workspaces.Data.Workspace", null) + .WithMany() + .HasForeignKey("WorkspaceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Channels.Data.Channel", b => + { + b.HasOne("Socialize.Api.Modules.Workspaces.Data.Workspace", null) + .WithMany() + .HasForeignKey("WorkspaceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Clients.Data.Client", b => + { + b.HasOne("Socialize.Api.Modules.Workspaces.Data.Workspace", null) + .WithMany() + .HasForeignKey("WorkspaceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Comments.Data.Comment", b => + { + b.HasOne("Socialize.Api.Modules.ContentItems.Data.ContentItem", null) + .WithMany() + .HasForeignKey("ContentItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Socialize.Api.Modules.Comments.Data.Comment", null) + .WithMany() + .HasForeignKey("ParentCommentId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Socialize.Api.Modules.Workspaces.Data.Workspace", null) + .WithMany() + .HasForeignKey("WorkspaceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.ContentItems.Data.ContentItem", b => + { + b.HasOne("Socialize.Api.Modules.Campaigns.Data.Campaign", null) + .WithMany() + .HasForeignKey("CampaignId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Socialize.Api.Modules.Clients.Data.Client", null) + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Socialize.Api.Modules.Workspaces.Data.Workspace", null) + .WithMany() + .HasForeignKey("WorkspaceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.ContentItems.Data.ContentItemActivityEntry", b => + { + b.HasOne("Socialize.Api.Modules.ContentItems.Data.ContentItem", null) + .WithMany() + .HasForeignKey("ContentItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Socialize.Api.Modules.Workspaces.Data.Workspace", null) + .WithMany() + .HasForeignKey("WorkspaceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.ContentItems.Data.ContentItemRevision", b => + { + b.HasOne("Socialize.Api.Modules.ContentItems.Data.ContentItem", null) + .WithMany() + .HasForeignKey("ContentItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Feedback.Data.FeedbackActivityEntry", b => + { + b.HasOne("Socialize.Api.Modules.Feedback.Data.FeedbackReport", "FeedbackReport") + .WithMany("ActivityEntries") + .HasForeignKey("FeedbackReportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("FeedbackReport"); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Feedback.Data.FeedbackComment", b => + { + b.HasOne("Socialize.Api.Modules.Feedback.Data.FeedbackReport", "FeedbackReport") + .WithMany("Comments") + .HasForeignKey("FeedbackReportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("FeedbackReport"); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Feedback.Data.FeedbackReport", b => + { + b.HasOne("Socialize.Api.Modules.Campaigns.Data.Campaign", null) + .WithMany() + .HasForeignKey("CampaignId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Socialize.Api.Modules.Clients.Data.Client", null) + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Socialize.Api.Modules.ContentItems.Data.ContentItem", null) + .WithMany() + .HasForeignKey("ContentItemId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Socialize.Api.Modules.Workspaces.Data.Workspace", null) + .WithMany() + .HasForeignKey("WorkspaceId") + .OnDelete(DeleteBehavior.SetNull); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Feedback.Data.FeedbackScreenshot", b => + { + b.HasOne("Socialize.Api.Modules.Feedback.Data.FeedbackReport", "FeedbackReport") + .WithOne("Screenshot") + .HasForeignKey("Socialize.Api.Modules.Feedback.Data.FeedbackScreenshot", "FeedbackReportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("FeedbackReport"); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Feedback.Data.FeedbackTag", b => + { + b.HasOne("Socialize.Api.Modules.Feedback.Data.FeedbackReport", "FeedbackReport") + .WithMany("Tags") + .HasForeignKey("FeedbackReportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("FeedbackReport"); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Notifications.Data.NotificationEvent", b => + { + b.HasOne("Socialize.Api.Modules.ContentItems.Data.ContentItem", null) + .WithMany() + .HasForeignKey("ContentItemId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Socialize.Api.Modules.Workspaces.Data.Workspace", null) + .WithMany() + .HasForeignKey("WorkspaceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Organizations.Data.Organization", b => + { + b.HasOne("Socialize.Api.Modules.Organizations.Data.OrganizationMembershipTier", null) + .WithMany() + .HasForeignKey("MembershipTierId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Organizations.Data.OrganizationMembership", b => + { + b.HasOne("Socialize.Api.Modules.Organizations.Data.Organization", null) + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Organizations.Data.OrganizationMembershipTierTranslation", b => + { + b.HasOne("Socialize.Api.Modules.Organizations.Data.OrganizationMembershipTier", null) + .WithMany() + .HasForeignKey("MembershipTierId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.ReleaseCommunications.Data.ReleaseCommit", b => + { + b.HasOne("Socialize.Api.Modules.ReleaseCommunications.Data.ReleaseUpdate", "ReleaseUpdate") + .WithMany() + .HasForeignKey("ReleaseUpdateId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ReleaseUpdate"); + }); + + modelBuilder.Entity("Socialize.Api.Modules.ReleaseCommunications.Data.ReleaseUpdateReadReceipt", b => + { + b.HasOne("Socialize.Api.Modules.ReleaseCommunications.Data.ReleaseUpdate", "ReleaseUpdate") + .WithMany("ReadReceipts") + .HasForeignKey("ReleaseUpdateId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ReleaseUpdate"); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Workspaces.Data.Workspace", b => + { + b.HasOne("Socialize.Api.Modules.Organizations.Data.Organization", null) + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Workspaces.Data.WorkspaceInvite", b => + { + b.HasOne("Socialize.Api.Modules.Workspaces.Data.Workspace", null) + .WithMany() + .HasForeignKey("WorkspaceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Feedback.Data.FeedbackReport", b => + { + b.Navigation("ActivityEntries"); + + b.Navigation("Comments"); + + b.Navigation("Screenshot"); + + b.Navigation("Tags"); + }); + + modelBuilder.Entity("Socialize.Api.Modules.ReleaseCommunications.Data.ReleaseUpdate", b => + { + b.Navigation("ReadReceipts"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/backend/src/Socialize.Api/Migrations/20260508031114_AddFrenchReleaseUpdateFields.cs b/backend/src/Socialize.Api/Migrations/20260508031114_AddFrenchReleaseUpdateFields.cs new file mode 100644 index 00000000..2ff568d3 --- /dev/null +++ b/backend/src/Socialize.Api/Migrations/20260508031114_AddFrenchReleaseUpdateFields.cs @@ -0,0 +1,49 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Socialize.Api.Migrations +{ + /// + internal partial class AddFrenchReleaseUpdateFields : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "SummaryFr", + table: "ReleaseUpdates", + type: "character varying(512)", + maxLength: 512, + nullable: false, + defaultValue: ""); + + migrationBuilder.AddColumn( + name: "TitleFr", + table: "ReleaseUpdates", + type: "character varying(160)", + maxLength: 160, + nullable: false, + defaultValue: ""); + + migrationBuilder.Sql(""" + UPDATE "ReleaseUpdates" + SET "TitleFr" = "Title", + "SummaryFr" = "Summary" + WHERE "TitleFr" = '' AND "SummaryFr" = ''; + """); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "SummaryFr", + table: "ReleaseUpdates"); + + migrationBuilder.DropColumn( + name: "TitleFr", + table: "ReleaseUpdates"); + } + } +} diff --git a/backend/src/Socialize.Api/Migrations/20260508034156_RemoveManualReleaseUpdateEmail.Designer.cs b/backend/src/Socialize.Api/Migrations/20260508034156_RemoveManualReleaseUpdateEmail.Designer.cs new file mode 100644 index 00000000..0f206f62 --- /dev/null +++ b/backend/src/Socialize.Api/Migrations/20260508034156_RemoveManualReleaseUpdateEmail.Designer.cs @@ -0,0 +1,2592 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Socialize.Api.Data; + +#nullable disable + +namespace Socialize.Api.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20260508034156_RemoveManualReleaseUpdateEmail")] + partial class RemoveManualReleaseUpdateEmail + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + 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("AspNetRoleClaims", (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("AspNetUserClaims", (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("AspNetUserLogins", (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("AspNetUserRoles", (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("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Approvals.Data.ApprovalDecision", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApprovalRequestId") + .HasColumnType("uuid"); + + b.Property("Comment") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("DecidedByEmail") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("DecidedByName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("DecidedByUserId") + .HasColumnType("uuid"); + + b.Property("Decision") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.HasKey("Id"); + + b.HasIndex("ApprovalRequestId"); + + b.ToTable("ApprovalDecisions", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Approvals.Data.ApprovalRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccessToken") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ContentItemId") + .HasColumnType("uuid"); + + b.Property("DueAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RequestedByUserId") + .HasColumnType("uuid"); + + b.Property("ReviewerEmail") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ReviewerName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("SentAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("Stage") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("State") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("WorkflowInstanceId") + .HasColumnType("uuid"); + + b.Property("WorkflowStepRequiredApproverCount") + .HasColumnType("integer"); + + b.Property("WorkflowStepSortOrder") + .HasColumnType("integer"); + + b.Property("WorkflowStepTargetType") + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("WorkflowStepTargetValue") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("WorkspaceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ContentItemId"); + + b.HasIndex("ReviewerEmail"); + + b.HasIndex("WorkflowInstanceId"); + + b.HasIndex("WorkspaceId"); + + b.ToTable("ApprovalRequests", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Approvals.Data.ApprovalWorkflowInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApprovalMode") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ContentItemId") + .HasColumnType("uuid"); + + b.Property("StartedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("State") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("WorkspaceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ContentItemId"); + + b.HasIndex("WorkspaceId"); + + b.HasIndex("ContentItemId", "State") + .IsUnique() + .HasFilter("\"State\" = 'Pending'"); + + b.ToTable("ApprovalWorkflowInstances", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Approvals.Data.WorkspaceApprovalStepConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("RequiredApproverCount") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(1); + + b.Property("SortOrder") + .HasColumnType("integer"); + + b.Property("TargetType") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("TargetValue") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("WorkspaceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("WorkspaceId"); + + b.HasIndex("WorkspaceId", "SortOrder") + .IsUnique(); + + b.ToTable("WorkspaceApprovalStepConfigurations", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Assets.Data.Asset", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AssetType") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("ContentItemId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("CurrentRevisionNumber") + .HasColumnType("integer"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("GoogleDriveFileId") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("GoogleDriveLink") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("PreviewUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("SourceType") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("WorkspaceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ContentItemId"); + + b.HasIndex("WorkspaceId"); + + b.ToTable("Assets", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Assets.Data.AssetRevision", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AssetId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("CreatedByUserId") + .HasColumnType("uuid"); + + b.Property("Notes") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("PreviewUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("RevisionNumber") + .HasColumnType("integer"); + + b.Property("SourceReference") + .IsRequired() + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.HasKey("Id"); + + b.HasIndex("AssetId"); + + b.HasIndex("AssetId", "RevisionNumber") + .IsUnique(); + + b.ToTable("AssetRevisions", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.CalendarIntegrations.Data.CalendarCatalogEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Category") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Country") + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("CultureOrReligion") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("DefaultColor") + .IsRequired() + .HasMaxLength(16) + .HasColumnType("character varying(16)"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("Language") + .IsRequired() + .HasMaxLength(16) + .HasColumnType("character varying(16)"); + + b.Property("ProviderName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("Region") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("SourceUrl") + .IsRequired() + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("TrustLevel") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.HasKey("Id"); + + b.HasIndex("Category"); + + b.HasIndex("Country"); + + b.HasIndex("ProviderName"); + + b.ToTable("CalendarCatalogEntries", (string)null); + + b.HasData( + new + { + Id = new Guid("10000000-0000-0000-0000-000000000001"), + Category = "public-holiday", + Country = "US", + CreatedAt = new DateTimeOffset(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + DefaultColor = "#2F80ED", + Description = "Federal public holiday calendar for the United States.", + Language = "en", + ProviderName = "Nager.Date", + SourceUrl = "https://date.nager.at/api/v3/PublicHolidays/2026/US", + Title = "United States Public Holidays", + TrustLevel = "Verified" + }, + new + { + Id = new Guid("10000000-0000-0000-0000-000000000002"), + Category = "public-holiday", + Country = "CA", + CreatedAt = new DateTimeOffset(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + DefaultColor = "#2F80ED", + Description = "Public holiday calendar for Canada.", + Language = "en", + ProviderName = "Nager.Date", + SourceUrl = "https://date.nager.at/api/v3/PublicHolidays/2026/CA", + Title = "Canada Public Holidays", + TrustLevel = "Verified" + }, + new + { + Id = new Guid("10000000-0000-0000-0000-000000000003"), + Category = "marketing-moment", + CreatedAt = new DateTimeOffset(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + DefaultColor = "#9B51E0", + Description = "Common retail, awareness, and social planning moments.", + Language = "en", + ProviderName = "Socialize", + SourceUrl = "https://example.com/socialize/marketing-moments.ics", + Title = "Common Marketing Moments", + TrustLevel = "Maintained" + }); + }); + + modelBuilder.Entity("Socialize.Api.Modules.CalendarIntegrations.Data.CalendarEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CalendarSourceId") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("EndDate") + .HasColumnType("date"); + + b.Property("EndLocalDateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("EndUtc") + .HasColumnType("timestamp with time zone"); + + b.Property("ImportedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsAllDay") + .HasColumnType("boolean"); + + b.Property("IsFloatingTime") + .HasColumnType("boolean"); + + b.Property("Location") + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("RecurrenceId") + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("SourceEventUid") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("SourceLastModifiedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("SourceUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("StartDate") + .HasColumnType("date"); + + b.Property("StartLocalDateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("StartUtc") + .HasColumnType("timestamp with time zone"); + + b.Property("TimeZoneId") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.HasKey("Id"); + + b.HasIndex("CalendarSourceId"); + + b.HasIndex("CalendarSourceId", "SourceEventUid", "StartDate") + .IsUnique(); + + b.ToTable("CalendarEvents", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.CalendarIntegrations.Data.CalendarSource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CatalogSourceReference") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Category") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Color") + .IsRequired() + .HasMaxLength(16) + .HasColumnType("character varying(16)"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("DisplayTitle") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("InheritanceMode") + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("IsEnabled") + .HasColumnType("boolean"); + + b.Property("LastAttemptedSyncAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastSuccessfulSyncAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastSyncError") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Scope") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("SourceUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("WorkspaceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("Scope"); + + b.HasIndex("UserId"); + + b.HasIndex("WorkspaceId"); + + b.ToTable("CalendarSources", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.CalendarIntegrations.Data.UserCalendarExportFeed", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("RevokedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Token") + .HasMaxLength(96) + .HasColumnType("character varying(96)"); + + b.Property("TokenHash") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TokenHash") + .IsUnique(); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("UserCalendarExportFeeds", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Campaigns.Data.Campaign", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClientId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("Description") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("EndDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Notes") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("StartDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("WorkspaceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.HasIndex("WorkspaceId"); + + b.HasIndex("ClientId", "Name") + .IsUnique(); + + b.ToTable("Campaigns", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Channels.Data.Channel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("ExternalUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("Handle") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Network") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("WorkspaceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("WorkspaceId"); + + b.HasIndex("WorkspaceId", "Network", "Name") + .IsUnique(); + + b.ToTable("Channels", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Clients.Data.Client", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PortraitUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("PrimaryContactEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PrimaryContactName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PrimaryContactPortraitUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("WorkspaceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("WorkspaceId"); + + b.HasIndex("WorkspaceId", "Name") + .IsUnique(); + + b.ToTable("Clients", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Comments.Data.Comment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AttachmentBlobContainerName") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("AttachmentBlobName") + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("AttachmentBlobUrl") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("AttachmentContentType") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("AttachmentFileName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("AttachmentSizeBytes") + .HasColumnType("bigint"); + + b.Property("AuthorDisplayName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("AuthorEmail") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("AuthorUserId") + .HasColumnType("uuid"); + + b.Property("Body") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("ContentItemId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("ParentCommentId") + .HasColumnType("uuid"); + + b.Property("WorkspaceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ContentItemId"); + + b.HasIndex("ParentCommentId"); + + b.HasIndex("WorkspaceId"); + + b.ToTable("Comments", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.ContentItems.Data.ContentItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CampaignId") + .HasColumnType("uuid"); + + b.Property("ClientId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("CurrentRevisionLabel") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("CurrentRevisionNumber") + .HasColumnType("integer"); + + b.Property("DueDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Hashtags") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("PublicationMessage") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("PublicationTargets") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("WorkspaceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("CampaignId"); + + b.HasIndex("ClientId"); + + b.HasIndex("WorkspaceId"); + + b.ToTable("ContentItems", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.ContentItems.Data.ContentItemActivityEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ActorEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ActorUserId") + .HasColumnType("uuid"); + + b.Property("ContentItemId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("EntityId") + .HasColumnType("uuid"); + + b.Property("EntityType") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("EventType") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("MetadataJson") + .HasColumnType("jsonb"); + + b.Property("Summary") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("WorkspaceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ContentItemId"); + + b.HasIndex("WorkspaceId"); + + b.HasIndex("ContentItemId", "CreatedAt"); + + b.ToTable("ContentItemActivityEntries", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.ContentItems.Data.ContentItemRevision", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ChangeSummary") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("ContentItemId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("CreatedByUserId") + .HasColumnType("uuid"); + + b.Property("Hashtags") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("PublicationMessage") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("PublicationTargets") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("RevisionLabel") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("RevisionNumber") + .HasColumnType("integer"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("ContentItemId"); + + b.HasIndex("ContentItemId", "RevisionNumber") + .IsUnique(); + + b.ToTable("ContentItemRevisions", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Feedback.Data.FeedbackActivityEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ActivityType") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("ActorDisplayName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ActorEmail") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ActorUserId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("FeedbackReportId") + .HasColumnType("uuid"); + + b.Property("FromValue") + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("Note") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("ToValue") + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.HasKey("Id"); + + b.HasIndex("ActorUserId"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("FeedbackReportId"); + + b.ToTable("FeedbackActivityEntries", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Feedback.Data.FeedbackComment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AuthorDisplayName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("AuthorEmail") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("AuthorRole") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("AuthorUserId") + .HasColumnType("uuid"); + + b.Property("Body") + .IsRequired() + .HasMaxLength(8000) + .HasColumnType("character varying(8000)"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("FeedbackReportId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AuthorUserId"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("FeedbackReportId"); + + b.ToTable("FeedbackComments", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Feedback.Data.FeedbackReport", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppVersion") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("BrowserUserAgent") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("CampaignId") + .HasColumnType("uuid"); + + b.Property("CampaignName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("CancellationReason") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("CancelledAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CancelledByUserId") + .HasColumnType("uuid"); + + b.Property("ClientId") + .HasColumnType("uuid"); + + b.Property("ClientName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ContentItemId") + .HasColumnType("uuid"); + + b.Property("ContentItemTitle") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(8000) + .HasColumnType("character varying(8000)"); + + b.Property("LastActivityAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ReporterDisplayName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ReporterEmail") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ReporterUserId") + .HasColumnType("uuid"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("SubmittedPath") + .IsRequired() + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("ViewportHeight") + .HasColumnType("integer"); + + b.Property("ViewportWidth") + .HasColumnType("integer"); + + b.Property("WorkspaceId") + .HasColumnType("uuid"); + + b.Property("WorkspaceName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("CampaignId"); + + b.HasIndex("ClientId"); + + b.HasIndex("ContentItemId"); + + b.HasIndex("LastActivityAt"); + + b.HasIndex("ReporterUserId"); + + b.HasIndex("Status"); + + b.HasIndex("Type"); + + b.HasIndex("WorkspaceId"); + + b.ToTable("FeedbackReports", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Feedback.Data.FeedbackScreenshot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BlobContainerName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("BlobName") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("ContentType") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("FeedbackReportId") + .HasColumnType("uuid"); + + b.Property("FileName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("FeedbackReportId") + .IsUnique(); + + b.ToTable("FeedbackScreenshots", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Feedback.Data.FeedbackTag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("FeedbackReportId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("NormalizedName") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName"); + + b.HasIndex("FeedbackReportId", "NormalizedName") + .IsUnique(); + + b.ToTable("FeedbackTags", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Identity.Data.Role", 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("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Identity.Data.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("Address") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Alias") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("BirthDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("FacebookId") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Firstname") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("GoogleId") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("LastAuthenticatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Lastname") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .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("PortraitUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("RefreshToken") + .HasMaxLength(44) + .HasColumnType("character varying(44)"); + + b.Property("RefreshTokenExpiryTime") + .HasColumnType("timestamp with time zone"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Notifications.Data.NotificationEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ContentItemId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("EntityId") + .HasColumnType("uuid"); + + b.Property("EntityType") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("EventType") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("MetadataJson") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("ReadAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RecipientEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("RecipientUserId") + .HasColumnType("uuid"); + + b.Property("WorkspaceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ContentItemId"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("RecipientUserId"); + + b.HasIndex("WorkspaceId"); + + b.ToTable("NotificationEvents", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Organizations.Data.Organization", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("LogoUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("MembershipTierId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasDefaultValue(new Guid("20000000-0000-0000-0000-000000000001")); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("OwnerUserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("MembershipTierId"); + + b.HasIndex("OwnerUserId"); + + b.ToTable("Organizations", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Organizations.Data.OrganizationMembership", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.HasIndex("OrganizationId", "UserId") + .IsUnique(); + + b.ToTable("OrganizationMemberships", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Organizations.Data.OrganizationMembershipTier", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ActiveContentLimit") + .HasColumnType("integer"); + + b.Property("ExternalReviewerLimit") + .HasColumnType("integer"); + + b.Property("IsCustom") + .HasColumnType("boolean"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("MemberLimit") + .HasColumnType("integer"); + + b.Property("MonthlyPriceCents") + .HasColumnType("integer"); + + b.Property("SortOrder") + .HasColumnType("integer"); + + b.Property("WorkspaceLimit") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Key") + .IsUnique(); + + b.HasIndex("SortOrder"); + + b.ToTable("OrganizationMembershipTiers", (string)null); + + b.HasData( + new + { + Id = new Guid("20000000-0000-0000-0000-000000000001"), + ActiveContentLimit = 3, + ExternalReviewerLimit = 1, + IsCustom = false, + Key = "free", + MemberLimit = 2, + MonthlyPriceCents = 0, + SortOrder = 10, + WorkspaceLimit = 1 + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000002"), + ActiveContentLimit = 25, + ExternalReviewerLimit = 10, + IsCustom = false, + Key = "freelance", + MemberLimit = 5, + MonthlyPriceCents = 1900, + SortOrder = 20, + WorkspaceLimit = 3 + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000003"), + ActiveContentLimit = 250, + IsCustom = false, + Key = "agency", + MemberLimit = 25, + MonthlyPriceCents = 7900, + SortOrder = 30, + WorkspaceLimit = 15 + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000004"), + IsCustom = true, + Key = "enterprise", + SortOrder = 40 + }); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Organizations.Data.OrganizationMembershipTierTranslation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Culture") + .IsRequired() + .HasMaxLength(16) + .HasColumnType("character varying(16)"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("MembershipTierId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.HasKey("Id"); + + b.HasIndex("MembershipTierId", "Culture") + .IsUnique(); + + b.ToTable("OrganizationMembershipTierTranslations", (string)null); + + b.HasData( + new + { + Id = new Guid("20000000-0000-0001-0000-000000000001"), + Culture = "en", + Description = "For trying Socialize on one real approval workflow.", + MembershipTierId = new Guid("20000000-0000-0000-0000-000000000001"), + Name = "Free" + }, + new + { + Id = new Guid("20000000-0000-0001-0000-000000000002"), + Culture = "fr", + Description = "Pour essayer Socialize sur un vrai workflow d'approbation.", + MembershipTierId = new Guid("20000000-0000-0000-0000-000000000001"), + Name = "Free" + }, + new + { + Id = new Guid("20000000-0000-0001-0000-000000000003"), + Culture = "en", + Description = "For solo operators managing recurring client reviews.", + MembershipTierId = new Guid("20000000-0000-0000-0000-000000000002"), + Name = "Freelance" + }, + new + { + Id = new Guid("20000000-0000-0001-0000-000000000004"), + Culture = "fr", + Description = "Pour les independants qui gerent des revisions client recurrentes.", + MembershipTierId = new Guid("20000000-0000-0000-0000-000000000002"), + Name = "Freelance" + }, + new + { + Id = new Guid("20000000-0000-0001-0000-000000000005"), + Culture = "en", + Description = "For agencies that need repeatable client approval operations.", + MembershipTierId = new Guid("20000000-0000-0000-0000-000000000003"), + Name = "Agency" + }, + new + { + Id = new Guid("20000000-0000-0001-0000-000000000006"), + Culture = "fr", + Description = "Pour les agences qui veulent des operations d'approbation client repetables.", + MembershipTierId = new Guid("20000000-0000-0000-0000-000000000003"), + Name = "Agency" + }, + new + { + Id = new Guid("20000000-0000-0001-0000-000000000007"), + Culture = "en", + Description = "For larger organizations with governance and access needs.", + MembershipTierId = new Guid("20000000-0000-0000-0000-000000000004"), + Name = "Enterprise" + }, + new + { + Id = new Guid("20000000-0000-0001-0000-000000000008"), + Culture = "fr", + Description = "Pour les grandes organisations avec des besoins de gouvernance et d'acces.", + MembershipTierId = new Guid("20000000-0000-0000-0000-000000000004"), + Name = "Enterprise" + }); + }); + + modelBuilder.Entity("Socialize.Api.Modules.ReleaseCommunications.Data.ReleaseCommit", b => + { + b.Property("Sha") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("AuthorEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("AuthorName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("AuthoredAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CommittedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CommunicationStatus") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("DeploymentLabel") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("ExternalUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("ImportedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("ReleaseUpdateId") + .HasColumnType("uuid"); + + b.Property("ShortSha") + .IsRequired() + .HasMaxLength(16) + .HasColumnType("character varying(16)"); + + b.Property("SourceBranch") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Subject") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Sha"); + + b.HasIndex("CommittedAt"); + + b.HasIndex("CommunicationStatus"); + + b.HasIndex("ReleaseUpdateId"); + + b.ToTable("ReleaseCommits", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.ReleaseCommunications.Data.ReleaseUpdate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ArchivedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("CreatedByUserId") + .HasColumnType("uuid"); + + b.Property("PublishedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("Summary") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("SummaryFr") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(160) + .HasColumnType("character varying(160)"); + + b.Property("TitleFr") + .IsRequired() + .HasMaxLength(160) + .HasColumnType("character varying(160)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("CreatedByUserId"); + + b.HasIndex("PublishedAt"); + + b.HasIndex("Status"); + + b.ToTable("ReleaseUpdates", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.ReleaseCommunications.Data.ReleaseUpdateEmailDigestReceipt", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("SentAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("UpdateCount") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("SentAt"); + + b.HasIndex("UserId"); + + b.ToTable("ReleaseUpdateEmailDigestReceipts", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.ReleaseCommunications.Data.ReleaseUpdateReadReceipt", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ReadAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("ReleaseUpdateId") + .HasColumnType("uuid"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("ReleaseUpdateId", "UserId") + .IsUnique(); + + b.ToTable("ReleaseUpdateReadReceipts", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Workspaces.Data.Workspace", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApprovalMode") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasDefaultValue("Required"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("LockContentAfterApproval") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("LogoUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("OwnerUserId") + .HasColumnType("uuid"); + + b.Property("SchedulePostsAutomaticallyOnApproval") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("SendAutomaticApprovalReminders") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("TimeZone") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("OwnerUserId"); + + b.ToTable("Workspaces", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Workspaces.Data.WorkspaceInvite", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("InvitedByUserId") + .HasColumnType("uuid"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("WorkspaceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("WorkspaceId"); + + b.HasIndex("WorkspaceId", "Email", "Status"); + + b.ToTable("WorkspaceInvites", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Socialize.Api.Modules.Identity.Data.Role", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Socialize.Api.Modules.Identity.Data.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Socialize.Api.Modules.Identity.Data.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Socialize.Api.Modules.Identity.Data.Role", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Socialize.Api.Modules.Identity.Data.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Socialize.Api.Modules.Identity.Data.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Approvals.Data.ApprovalDecision", b => + { + b.HasOne("Socialize.Api.Modules.Approvals.Data.ApprovalRequest", null) + .WithMany() + .HasForeignKey("ApprovalRequestId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Approvals.Data.ApprovalRequest", b => + { + b.HasOne("Socialize.Api.Modules.ContentItems.Data.ContentItem", null) + .WithMany() + .HasForeignKey("ContentItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Socialize.Api.Modules.Approvals.Data.ApprovalWorkflowInstance", null) + .WithMany() + .HasForeignKey("WorkflowInstanceId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Socialize.Api.Modules.Workspaces.Data.Workspace", null) + .WithMany() + .HasForeignKey("WorkspaceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Approvals.Data.ApprovalWorkflowInstance", b => + { + b.HasOne("Socialize.Api.Modules.ContentItems.Data.ContentItem", null) + .WithMany() + .HasForeignKey("ContentItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Socialize.Api.Modules.Workspaces.Data.Workspace", null) + .WithMany() + .HasForeignKey("WorkspaceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Approvals.Data.WorkspaceApprovalStepConfiguration", b => + { + b.HasOne("Socialize.Api.Modules.Workspaces.Data.Workspace", null) + .WithMany() + .HasForeignKey("WorkspaceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Assets.Data.Asset", b => + { + b.HasOne("Socialize.Api.Modules.ContentItems.Data.ContentItem", null) + .WithMany() + .HasForeignKey("ContentItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Socialize.Api.Modules.Workspaces.Data.Workspace", null) + .WithMany() + .HasForeignKey("WorkspaceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Assets.Data.AssetRevision", b => + { + b.HasOne("Socialize.Api.Modules.Assets.Data.Asset", null) + .WithMany() + .HasForeignKey("AssetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.CalendarIntegrations.Data.CalendarEvent", b => + { + b.HasOne("Socialize.Api.Modules.CalendarIntegrations.Data.CalendarSource", null) + .WithMany() + .HasForeignKey("CalendarSourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Campaigns.Data.Campaign", b => + { + b.HasOne("Socialize.Api.Modules.Clients.Data.Client", null) + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Socialize.Api.Modules.Workspaces.Data.Workspace", null) + .WithMany() + .HasForeignKey("WorkspaceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Channels.Data.Channel", b => + { + b.HasOne("Socialize.Api.Modules.Workspaces.Data.Workspace", null) + .WithMany() + .HasForeignKey("WorkspaceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Clients.Data.Client", b => + { + b.HasOne("Socialize.Api.Modules.Workspaces.Data.Workspace", null) + .WithMany() + .HasForeignKey("WorkspaceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Comments.Data.Comment", b => + { + b.HasOne("Socialize.Api.Modules.ContentItems.Data.ContentItem", null) + .WithMany() + .HasForeignKey("ContentItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Socialize.Api.Modules.Comments.Data.Comment", null) + .WithMany() + .HasForeignKey("ParentCommentId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Socialize.Api.Modules.Workspaces.Data.Workspace", null) + .WithMany() + .HasForeignKey("WorkspaceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.ContentItems.Data.ContentItem", b => + { + b.HasOne("Socialize.Api.Modules.Campaigns.Data.Campaign", null) + .WithMany() + .HasForeignKey("CampaignId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Socialize.Api.Modules.Clients.Data.Client", null) + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Socialize.Api.Modules.Workspaces.Data.Workspace", null) + .WithMany() + .HasForeignKey("WorkspaceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.ContentItems.Data.ContentItemActivityEntry", b => + { + b.HasOne("Socialize.Api.Modules.ContentItems.Data.ContentItem", null) + .WithMany() + .HasForeignKey("ContentItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Socialize.Api.Modules.Workspaces.Data.Workspace", null) + .WithMany() + .HasForeignKey("WorkspaceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.ContentItems.Data.ContentItemRevision", b => + { + b.HasOne("Socialize.Api.Modules.ContentItems.Data.ContentItem", null) + .WithMany() + .HasForeignKey("ContentItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Feedback.Data.FeedbackActivityEntry", b => + { + b.HasOne("Socialize.Api.Modules.Feedback.Data.FeedbackReport", "FeedbackReport") + .WithMany("ActivityEntries") + .HasForeignKey("FeedbackReportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("FeedbackReport"); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Feedback.Data.FeedbackComment", b => + { + b.HasOne("Socialize.Api.Modules.Feedback.Data.FeedbackReport", "FeedbackReport") + .WithMany("Comments") + .HasForeignKey("FeedbackReportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("FeedbackReport"); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Feedback.Data.FeedbackReport", b => + { + b.HasOne("Socialize.Api.Modules.Campaigns.Data.Campaign", null) + .WithMany() + .HasForeignKey("CampaignId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Socialize.Api.Modules.Clients.Data.Client", null) + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Socialize.Api.Modules.ContentItems.Data.ContentItem", null) + .WithMany() + .HasForeignKey("ContentItemId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Socialize.Api.Modules.Workspaces.Data.Workspace", null) + .WithMany() + .HasForeignKey("WorkspaceId") + .OnDelete(DeleteBehavior.SetNull); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Feedback.Data.FeedbackScreenshot", b => + { + b.HasOne("Socialize.Api.Modules.Feedback.Data.FeedbackReport", "FeedbackReport") + .WithOne("Screenshot") + .HasForeignKey("Socialize.Api.Modules.Feedback.Data.FeedbackScreenshot", "FeedbackReportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("FeedbackReport"); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Feedback.Data.FeedbackTag", b => + { + b.HasOne("Socialize.Api.Modules.Feedback.Data.FeedbackReport", "FeedbackReport") + .WithMany("Tags") + .HasForeignKey("FeedbackReportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("FeedbackReport"); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Notifications.Data.NotificationEvent", b => + { + b.HasOne("Socialize.Api.Modules.ContentItems.Data.ContentItem", null) + .WithMany() + .HasForeignKey("ContentItemId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Socialize.Api.Modules.Workspaces.Data.Workspace", null) + .WithMany() + .HasForeignKey("WorkspaceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Organizations.Data.Organization", b => + { + b.HasOne("Socialize.Api.Modules.Organizations.Data.OrganizationMembershipTier", null) + .WithMany() + .HasForeignKey("MembershipTierId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Organizations.Data.OrganizationMembership", b => + { + b.HasOne("Socialize.Api.Modules.Organizations.Data.Organization", null) + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Organizations.Data.OrganizationMembershipTierTranslation", b => + { + b.HasOne("Socialize.Api.Modules.Organizations.Data.OrganizationMembershipTier", null) + .WithMany() + .HasForeignKey("MembershipTierId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.ReleaseCommunications.Data.ReleaseCommit", b => + { + b.HasOne("Socialize.Api.Modules.ReleaseCommunications.Data.ReleaseUpdate", "ReleaseUpdate") + .WithMany() + .HasForeignKey("ReleaseUpdateId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ReleaseUpdate"); + }); + + modelBuilder.Entity("Socialize.Api.Modules.ReleaseCommunications.Data.ReleaseUpdateReadReceipt", b => + { + b.HasOne("Socialize.Api.Modules.ReleaseCommunications.Data.ReleaseUpdate", "ReleaseUpdate") + .WithMany("ReadReceipts") + .HasForeignKey("ReleaseUpdateId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ReleaseUpdate"); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Workspaces.Data.Workspace", b => + { + b.HasOne("Socialize.Api.Modules.Organizations.Data.Organization", null) + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Workspaces.Data.WorkspaceInvite", b => + { + b.HasOne("Socialize.Api.Modules.Workspaces.Data.Workspace", null) + .WithMany() + .HasForeignKey("WorkspaceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Feedback.Data.FeedbackReport", b => + { + b.Navigation("ActivityEntries"); + + b.Navigation("Comments"); + + b.Navigation("Screenshot"); + + b.Navigation("Tags"); + }); + + modelBuilder.Entity("Socialize.Api.Modules.ReleaseCommunications.Data.ReleaseUpdate", b => + { + b.Navigation("ReadReceipts"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/backend/src/Socialize.Api/Migrations/20260508034156_RemoveManualReleaseUpdateEmail.cs b/backend/src/Socialize.Api/Migrations/20260508034156_RemoveManualReleaseUpdateEmail.cs new file mode 100644 index 00000000..c8ee4e42 --- /dev/null +++ b/backend/src/Socialize.Api/Migrations/20260508034156_RemoveManualReleaseUpdateEmail.cs @@ -0,0 +1,53 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Socialize.Api.Migrations +{ + /// + internal partial class RemoveManualReleaseUpdateEmail : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + ArgumentNullException.ThrowIfNull(migrationBuilder); + + migrationBuilder.DropColumn( + name: "ManualEmailRecipientCount", + table: "ReleaseUpdates"); + + migrationBuilder.DropColumn( + name: "ManualEmailSentAt", + table: "ReleaseUpdates"); + + migrationBuilder.DropColumn( + name: "ManualEmailSentByUserId", + table: "ReleaseUpdates"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + ArgumentNullException.ThrowIfNull(migrationBuilder); + + migrationBuilder.AddColumn( + name: "ManualEmailRecipientCount", + table: "ReleaseUpdates", + type: "integer", + nullable: true); + + migrationBuilder.AddColumn( + name: "ManualEmailSentAt", + table: "ReleaseUpdates", + type: "timestamp with time zone", + nullable: true); + + migrationBuilder.AddColumn( + name: "ManualEmailSentByUserId", + table: "ReleaseUpdates", + type: "uuid", + nullable: true); + } + } +} diff --git a/backend/src/Socialize.Api/Migrations/20260508034902_ExpandReleaseUpdateDescriptions.Designer.cs b/backend/src/Socialize.Api/Migrations/20260508034902_ExpandReleaseUpdateDescriptions.Designer.cs new file mode 100644 index 00000000..d1efc630 --- /dev/null +++ b/backend/src/Socialize.Api/Migrations/20260508034902_ExpandReleaseUpdateDescriptions.Designer.cs @@ -0,0 +1,2592 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Socialize.Api.Data; + +#nullable disable + +namespace Socialize.Api.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20260508034902_ExpandReleaseUpdateDescriptions")] + partial class ExpandReleaseUpdateDescriptions + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + 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("AspNetRoleClaims", (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("AspNetUserClaims", (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("AspNetUserLogins", (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("AspNetUserRoles", (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("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Approvals.Data.ApprovalDecision", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApprovalRequestId") + .HasColumnType("uuid"); + + b.Property("Comment") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("DecidedByEmail") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("DecidedByName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("DecidedByUserId") + .HasColumnType("uuid"); + + b.Property("Decision") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.HasKey("Id"); + + b.HasIndex("ApprovalRequestId"); + + b.ToTable("ApprovalDecisions", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Approvals.Data.ApprovalRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccessToken") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ContentItemId") + .HasColumnType("uuid"); + + b.Property("DueAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RequestedByUserId") + .HasColumnType("uuid"); + + b.Property("ReviewerEmail") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ReviewerName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("SentAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("Stage") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("State") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("WorkflowInstanceId") + .HasColumnType("uuid"); + + b.Property("WorkflowStepRequiredApproverCount") + .HasColumnType("integer"); + + b.Property("WorkflowStepSortOrder") + .HasColumnType("integer"); + + b.Property("WorkflowStepTargetType") + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("WorkflowStepTargetValue") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("WorkspaceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ContentItemId"); + + b.HasIndex("ReviewerEmail"); + + b.HasIndex("WorkflowInstanceId"); + + b.HasIndex("WorkspaceId"); + + b.ToTable("ApprovalRequests", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Approvals.Data.ApprovalWorkflowInstance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApprovalMode") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ContentItemId") + .HasColumnType("uuid"); + + b.Property("StartedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("State") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("WorkspaceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ContentItemId"); + + b.HasIndex("WorkspaceId"); + + b.HasIndex("ContentItemId", "State") + .IsUnique() + .HasFilter("\"State\" = 'Pending'"); + + b.ToTable("ApprovalWorkflowInstances", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Approvals.Data.WorkspaceApprovalStepConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("RequiredApproverCount") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(1); + + b.Property("SortOrder") + .HasColumnType("integer"); + + b.Property("TargetType") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("TargetValue") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("WorkspaceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("WorkspaceId"); + + b.HasIndex("WorkspaceId", "SortOrder") + .IsUnique(); + + b.ToTable("WorkspaceApprovalStepConfigurations", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Assets.Data.Asset", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AssetType") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("ContentItemId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("CurrentRevisionNumber") + .HasColumnType("integer"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("GoogleDriveFileId") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("GoogleDriveLink") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("PreviewUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("SourceType") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("WorkspaceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ContentItemId"); + + b.HasIndex("WorkspaceId"); + + b.ToTable("Assets", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Assets.Data.AssetRevision", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AssetId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("CreatedByUserId") + .HasColumnType("uuid"); + + b.Property("Notes") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("PreviewUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("RevisionNumber") + .HasColumnType("integer"); + + b.Property("SourceReference") + .IsRequired() + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.HasKey("Id"); + + b.HasIndex("AssetId"); + + b.HasIndex("AssetId", "RevisionNumber") + .IsUnique(); + + b.ToTable("AssetRevisions", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.CalendarIntegrations.Data.CalendarCatalogEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Category") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Country") + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("CultureOrReligion") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("DefaultColor") + .IsRequired() + .HasMaxLength(16) + .HasColumnType("character varying(16)"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("Language") + .IsRequired() + .HasMaxLength(16) + .HasColumnType("character varying(16)"); + + b.Property("ProviderName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("Region") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("SourceUrl") + .IsRequired() + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("TrustLevel") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.HasKey("Id"); + + b.HasIndex("Category"); + + b.HasIndex("Country"); + + b.HasIndex("ProviderName"); + + b.ToTable("CalendarCatalogEntries", (string)null); + + b.HasData( + new + { + Id = new Guid("10000000-0000-0000-0000-000000000001"), + Category = "public-holiday", + Country = "US", + CreatedAt = new DateTimeOffset(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + DefaultColor = "#2F80ED", + Description = "Federal public holiday calendar for the United States.", + Language = "en", + ProviderName = "Nager.Date", + SourceUrl = "https://date.nager.at/api/v3/PublicHolidays/2026/US", + Title = "United States Public Holidays", + TrustLevel = "Verified" + }, + new + { + Id = new Guid("10000000-0000-0000-0000-000000000002"), + Category = "public-holiday", + Country = "CA", + CreatedAt = new DateTimeOffset(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + DefaultColor = "#2F80ED", + Description = "Public holiday calendar for Canada.", + Language = "en", + ProviderName = "Nager.Date", + SourceUrl = "https://date.nager.at/api/v3/PublicHolidays/2026/CA", + Title = "Canada Public Holidays", + TrustLevel = "Verified" + }, + new + { + Id = new Guid("10000000-0000-0000-0000-000000000003"), + Category = "marketing-moment", + CreatedAt = new DateTimeOffset(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)), + DefaultColor = "#9B51E0", + Description = "Common retail, awareness, and social planning moments.", + Language = "en", + ProviderName = "Socialize", + SourceUrl = "https://example.com/socialize/marketing-moments.ics", + Title = "Common Marketing Moments", + TrustLevel = "Maintained" + }); + }); + + modelBuilder.Entity("Socialize.Api.Modules.CalendarIntegrations.Data.CalendarEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CalendarSourceId") + .HasColumnType("uuid"); + + b.Property("Description") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("EndDate") + .HasColumnType("date"); + + b.Property("EndLocalDateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("EndUtc") + .HasColumnType("timestamp with time zone"); + + b.Property("ImportedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsAllDay") + .HasColumnType("boolean"); + + b.Property("IsFloatingTime") + .HasColumnType("boolean"); + + b.Property("Location") + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("RecurrenceId") + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("SourceEventUid") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("SourceLastModifiedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("SourceUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("StartDate") + .HasColumnType("date"); + + b.Property("StartLocalDateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("StartUtc") + .HasColumnType("timestamp with time zone"); + + b.Property("TimeZoneId") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.HasKey("Id"); + + b.HasIndex("CalendarSourceId"); + + b.HasIndex("CalendarSourceId", "SourceEventUid", "StartDate") + .IsUnique(); + + b.ToTable("CalendarEvents", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.CalendarIntegrations.Data.CalendarSource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CatalogSourceReference") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Category") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Color") + .IsRequired() + .HasMaxLength(16) + .HasColumnType("character varying(16)"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("DisplayTitle") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("InheritanceMode") + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("IsEnabled") + .HasColumnType("boolean"); + + b.Property("LastAttemptedSyncAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastSuccessfulSyncAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastSyncError") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Scope") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("SourceUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("WorkspaceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("Scope"); + + b.HasIndex("UserId"); + + b.HasIndex("WorkspaceId"); + + b.ToTable("CalendarSources", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.CalendarIntegrations.Data.UserCalendarExportFeed", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("RevokedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Token") + .HasMaxLength(96) + .HasColumnType("character varying(96)"); + + b.Property("TokenHash") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("TokenHash") + .IsUnique(); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("UserCalendarExportFeeds", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Campaigns.Data.Campaign", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ClientId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("Description") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("EndDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Notes") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("StartDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("WorkspaceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.HasIndex("WorkspaceId"); + + b.HasIndex("ClientId", "Name") + .IsUnique(); + + b.ToTable("Campaigns", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Channels.Data.Channel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("ExternalUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("Handle") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Network") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("WorkspaceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("WorkspaceId"); + + b.HasIndex("WorkspaceId", "Network", "Name") + .IsUnique(); + + b.ToTable("Channels", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Clients.Data.Client", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PortraitUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("PrimaryContactEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PrimaryContactName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PrimaryContactPortraitUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("WorkspaceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("WorkspaceId"); + + b.HasIndex("WorkspaceId", "Name") + .IsUnique(); + + b.ToTable("Clients", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Comments.Data.Comment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AttachmentBlobContainerName") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("AttachmentBlobName") + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("AttachmentBlobUrl") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("AttachmentContentType") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("AttachmentFileName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("AttachmentSizeBytes") + .HasColumnType("bigint"); + + b.Property("AuthorDisplayName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("AuthorEmail") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("AuthorUserId") + .HasColumnType("uuid"); + + b.Property("Body") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("ContentItemId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("ParentCommentId") + .HasColumnType("uuid"); + + b.Property("WorkspaceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ContentItemId"); + + b.HasIndex("ParentCommentId"); + + b.HasIndex("WorkspaceId"); + + b.ToTable("Comments", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.ContentItems.Data.ContentItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CampaignId") + .HasColumnType("uuid"); + + b.Property("ClientId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("CurrentRevisionLabel") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("CurrentRevisionNumber") + .HasColumnType("integer"); + + b.Property("DueDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Hashtags") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("PublicationMessage") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("PublicationTargets") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("WorkspaceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("CampaignId"); + + b.HasIndex("ClientId"); + + b.HasIndex("WorkspaceId"); + + b.ToTable("ContentItems", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.ContentItems.Data.ContentItemActivityEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ActorEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ActorUserId") + .HasColumnType("uuid"); + + b.Property("ContentItemId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("EntityId") + .HasColumnType("uuid"); + + b.Property("EntityType") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("EventType") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("MetadataJson") + .HasColumnType("jsonb"); + + b.Property("Summary") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("WorkspaceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ContentItemId"); + + b.HasIndex("WorkspaceId"); + + b.HasIndex("ContentItemId", "CreatedAt"); + + b.ToTable("ContentItemActivityEntries", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.ContentItems.Data.ContentItemRevision", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ChangeSummary") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("ContentItemId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("CreatedByUserId") + .HasColumnType("uuid"); + + b.Property("Hashtags") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("PublicationMessage") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("PublicationTargets") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("RevisionLabel") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("RevisionNumber") + .HasColumnType("integer"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("ContentItemId"); + + b.HasIndex("ContentItemId", "RevisionNumber") + .IsUnique(); + + b.ToTable("ContentItemRevisions", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Feedback.Data.FeedbackActivityEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ActivityType") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("ActorDisplayName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ActorEmail") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ActorUserId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("FeedbackReportId") + .HasColumnType("uuid"); + + b.Property("FromValue") + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("Note") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("ToValue") + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.HasKey("Id"); + + b.HasIndex("ActorUserId"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("FeedbackReportId"); + + b.ToTable("FeedbackActivityEntries", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Feedback.Data.FeedbackComment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AuthorDisplayName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("AuthorEmail") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("AuthorRole") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("AuthorUserId") + .HasColumnType("uuid"); + + b.Property("Body") + .IsRequired() + .HasMaxLength(8000) + .HasColumnType("character varying(8000)"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("FeedbackReportId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("AuthorUserId"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("FeedbackReportId"); + + b.ToTable("FeedbackComments", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Feedback.Data.FeedbackReport", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AppVersion") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("BrowserUserAgent") + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("CampaignId") + .HasColumnType("uuid"); + + b.Property("CampaignName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("CancellationReason") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("CancelledAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CancelledByUserId") + .HasColumnType("uuid"); + + b.Property("ClientId") + .HasColumnType("uuid"); + + b.Property("ClientName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ContentItemId") + .HasColumnType("uuid"); + + b.Property("ContentItemTitle") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(8000) + .HasColumnType("character varying(8000)"); + + b.Property("LastActivityAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ReporterDisplayName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ReporterEmail") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ReporterUserId") + .HasColumnType("uuid"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("SubmittedPath") + .IsRequired() + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("ViewportHeight") + .HasColumnType("integer"); + + b.Property("ViewportWidth") + .HasColumnType("integer"); + + b.Property("WorkspaceId") + .HasColumnType("uuid"); + + b.Property("WorkspaceName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("CampaignId"); + + b.HasIndex("ClientId"); + + b.HasIndex("ContentItemId"); + + b.HasIndex("LastActivityAt"); + + b.HasIndex("ReporterUserId"); + + b.HasIndex("Status"); + + b.HasIndex("Type"); + + b.HasIndex("WorkspaceId"); + + b.ToTable("FeedbackReports", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Feedback.Data.FeedbackScreenshot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BlobContainerName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("BlobName") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("ContentType") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("FeedbackReportId") + .HasColumnType("uuid"); + + b.Property("FileName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("SizeBytes") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("FeedbackReportId") + .IsUnique(); + + b.ToTable("FeedbackScreenshots", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Feedback.Data.FeedbackTag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("FeedbackReportId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("NormalizedName") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName"); + + b.HasIndex("FeedbackReportId", "NormalizedName") + .IsUnique(); + + b.ToTable("FeedbackTags", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Identity.Data.Role", 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("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Identity.Data.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("Address") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Alias") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("BirthDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("FacebookId") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Firstname") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("GoogleId") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("LastAuthenticatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Lastname") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .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("PortraitUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("RefreshToken") + .HasMaxLength(44) + .HasColumnType("character varying(44)"); + + b.Property("RefreshTokenExpiryTime") + .HasColumnType("timestamp with time zone"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Notifications.Data.NotificationEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ContentItemId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("EntityId") + .HasColumnType("uuid"); + + b.Property("EntityType") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("EventType") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("character varying(1024)"); + + b.Property("MetadataJson") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("ReadAt") + .HasColumnType("timestamp with time zone"); + + b.Property("RecipientEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("RecipientUserId") + .HasColumnType("uuid"); + + b.Property("WorkspaceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ContentItemId"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("RecipientUserId"); + + b.HasIndex("WorkspaceId"); + + b.ToTable("NotificationEvents", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Organizations.Data.Organization", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("LogoUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("MembershipTierId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasDefaultValue(new Guid("20000000-0000-0000-0000-000000000001")); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("OwnerUserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("MembershipTierId"); + + b.HasIndex("OwnerUserId"); + + b.ToTable("Organizations", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Organizations.Data.OrganizationMembership", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.HasIndex("OrganizationId", "UserId") + .IsUnique(); + + b.ToTable("OrganizationMemberships", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Organizations.Data.OrganizationMembershipTier", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ActiveContentLimit") + .HasColumnType("integer"); + + b.Property("ExternalReviewerLimit") + .HasColumnType("integer"); + + b.Property("IsCustom") + .HasColumnType("boolean"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("MemberLimit") + .HasColumnType("integer"); + + b.Property("MonthlyPriceCents") + .HasColumnType("integer"); + + b.Property("SortOrder") + .HasColumnType("integer"); + + b.Property("WorkspaceLimit") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Key") + .IsUnique(); + + b.HasIndex("SortOrder"); + + b.ToTable("OrganizationMembershipTiers", (string)null); + + b.HasData( + new + { + Id = new Guid("20000000-0000-0000-0000-000000000001"), + ActiveContentLimit = 3, + ExternalReviewerLimit = 1, + IsCustom = false, + Key = "free", + MemberLimit = 2, + MonthlyPriceCents = 0, + SortOrder = 10, + WorkspaceLimit = 1 + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000002"), + ActiveContentLimit = 25, + ExternalReviewerLimit = 10, + IsCustom = false, + Key = "freelance", + MemberLimit = 5, + MonthlyPriceCents = 1900, + SortOrder = 20, + WorkspaceLimit = 3 + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000003"), + ActiveContentLimit = 250, + IsCustom = false, + Key = "agency", + MemberLimit = 25, + MonthlyPriceCents = 7900, + SortOrder = 30, + WorkspaceLimit = 15 + }, + new + { + Id = new Guid("20000000-0000-0000-0000-000000000004"), + IsCustom = true, + Key = "enterprise", + SortOrder = 40 + }); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Organizations.Data.OrganizationMembershipTierTranslation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Culture") + .IsRequired() + .HasMaxLength(16) + .HasColumnType("character varying(16)"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("MembershipTierId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.HasKey("Id"); + + b.HasIndex("MembershipTierId", "Culture") + .IsUnique(); + + b.ToTable("OrganizationMembershipTierTranslations", (string)null); + + b.HasData( + new + { + Id = new Guid("20000000-0000-0001-0000-000000000001"), + Culture = "en", + Description = "For trying Socialize on one real approval workflow.", + MembershipTierId = new Guid("20000000-0000-0000-0000-000000000001"), + Name = "Free" + }, + new + { + Id = new Guid("20000000-0000-0001-0000-000000000002"), + Culture = "fr", + Description = "Pour essayer Socialize sur un vrai workflow d'approbation.", + MembershipTierId = new Guid("20000000-0000-0000-0000-000000000001"), + Name = "Free" + }, + new + { + Id = new Guid("20000000-0000-0001-0000-000000000003"), + Culture = "en", + Description = "For solo operators managing recurring client reviews.", + MembershipTierId = new Guid("20000000-0000-0000-0000-000000000002"), + Name = "Freelance" + }, + new + { + Id = new Guid("20000000-0000-0001-0000-000000000004"), + Culture = "fr", + Description = "Pour les independants qui gerent des revisions client recurrentes.", + MembershipTierId = new Guid("20000000-0000-0000-0000-000000000002"), + Name = "Freelance" + }, + new + { + Id = new Guid("20000000-0000-0001-0000-000000000005"), + Culture = "en", + Description = "For agencies that need repeatable client approval operations.", + MembershipTierId = new Guid("20000000-0000-0000-0000-000000000003"), + Name = "Agency" + }, + new + { + Id = new Guid("20000000-0000-0001-0000-000000000006"), + Culture = "fr", + Description = "Pour les agences qui veulent des operations d'approbation client repetables.", + MembershipTierId = new Guid("20000000-0000-0000-0000-000000000003"), + Name = "Agency" + }, + new + { + Id = new Guid("20000000-0000-0001-0000-000000000007"), + Culture = "en", + Description = "For larger organizations with governance and access needs.", + MembershipTierId = new Guid("20000000-0000-0000-0000-000000000004"), + Name = "Enterprise" + }, + new + { + Id = new Guid("20000000-0000-0001-0000-000000000008"), + Culture = "fr", + Description = "Pour les grandes organisations avec des besoins de gouvernance et d'acces.", + MembershipTierId = new Guid("20000000-0000-0000-0000-000000000004"), + Name = "Enterprise" + }); + }); + + modelBuilder.Entity("Socialize.Api.Modules.ReleaseCommunications.Data.ReleaseCommit", b => + { + b.Property("Sha") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("AuthorEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("AuthorName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("AuthoredAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CommittedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CommunicationStatus") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("DeploymentLabel") + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("ExternalUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("ImportedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("ReleaseUpdateId") + .HasColumnType("uuid"); + + b.Property("ShortSha") + .IsRequired() + .HasMaxLength(16) + .HasColumnType("character varying(16)"); + + b.Property("SourceBranch") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Subject") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Sha"); + + b.HasIndex("CommittedAt"); + + b.HasIndex("CommunicationStatus"); + + b.HasIndex("ReleaseUpdateId"); + + b.ToTable("ReleaseCommits", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.ReleaseCommunications.Data.ReleaseUpdate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ArchivedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("CreatedByUserId") + .HasColumnType("uuid"); + + b.Property("PublishedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("Summary") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("SummaryFr") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(160) + .HasColumnType("character varying(160)"); + + b.Property("TitleFr") + .IsRequired() + .HasMaxLength(160) + .HasColumnType("character varying(160)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("CreatedByUserId"); + + b.HasIndex("PublishedAt"); + + b.HasIndex("Status"); + + b.ToTable("ReleaseUpdates", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.ReleaseCommunications.Data.ReleaseUpdateEmailDigestReceipt", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("SentAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("UpdateCount") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("SentAt"); + + b.HasIndex("UserId"); + + b.ToTable("ReleaseUpdateEmailDigestReceipts", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.ReleaseCommunications.Data.ReleaseUpdateReadReceipt", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ReadAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("ReleaseUpdateId") + .HasColumnType("uuid"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("ReleaseUpdateId", "UserId") + .IsUnique(); + + b.ToTable("ReleaseUpdateReadReceipts", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Workspaces.Data.Workspace", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ApprovalMode") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(32) + .HasColumnType("character varying(32)") + .HasDefaultValue("Required"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("LockContentAfterApproval") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("LogoUrl") + .HasMaxLength(2048) + .HasColumnType("character varying(2048)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("OwnerUserId") + .HasColumnType("uuid"); + + b.Property("SchedulePostsAutomaticallyOnApproval") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("SendAutomaticApprovalReminders") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("TimeZone") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("OwnerUserId"); + + b.ToTable("Workspaces", (string)null); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Workspaces.Data.WorkspaceInvite", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("InvitedByUserId") + .HasColumnType("uuid"); + + b.Property("Role") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("WorkspaceId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("WorkspaceId"); + + b.HasIndex("WorkspaceId", "Email", "Status"); + + b.ToTable("WorkspaceInvites", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Socialize.Api.Modules.Identity.Data.Role", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Socialize.Api.Modules.Identity.Data.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Socialize.Api.Modules.Identity.Data.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Socialize.Api.Modules.Identity.Data.Role", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Socialize.Api.Modules.Identity.Data.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Socialize.Api.Modules.Identity.Data.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Approvals.Data.ApprovalDecision", b => + { + b.HasOne("Socialize.Api.Modules.Approvals.Data.ApprovalRequest", null) + .WithMany() + .HasForeignKey("ApprovalRequestId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Approvals.Data.ApprovalRequest", b => + { + b.HasOne("Socialize.Api.Modules.ContentItems.Data.ContentItem", null) + .WithMany() + .HasForeignKey("ContentItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Socialize.Api.Modules.Approvals.Data.ApprovalWorkflowInstance", null) + .WithMany() + .HasForeignKey("WorkflowInstanceId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Socialize.Api.Modules.Workspaces.Data.Workspace", null) + .WithMany() + .HasForeignKey("WorkspaceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Approvals.Data.ApprovalWorkflowInstance", b => + { + b.HasOne("Socialize.Api.Modules.ContentItems.Data.ContentItem", null) + .WithMany() + .HasForeignKey("ContentItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Socialize.Api.Modules.Workspaces.Data.Workspace", null) + .WithMany() + .HasForeignKey("WorkspaceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Approvals.Data.WorkspaceApprovalStepConfiguration", b => + { + b.HasOne("Socialize.Api.Modules.Workspaces.Data.Workspace", null) + .WithMany() + .HasForeignKey("WorkspaceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Assets.Data.Asset", b => + { + b.HasOne("Socialize.Api.Modules.ContentItems.Data.ContentItem", null) + .WithMany() + .HasForeignKey("ContentItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Socialize.Api.Modules.Workspaces.Data.Workspace", null) + .WithMany() + .HasForeignKey("WorkspaceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Assets.Data.AssetRevision", b => + { + b.HasOne("Socialize.Api.Modules.Assets.Data.Asset", null) + .WithMany() + .HasForeignKey("AssetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.CalendarIntegrations.Data.CalendarEvent", b => + { + b.HasOne("Socialize.Api.Modules.CalendarIntegrations.Data.CalendarSource", null) + .WithMany() + .HasForeignKey("CalendarSourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Campaigns.Data.Campaign", b => + { + b.HasOne("Socialize.Api.Modules.Clients.Data.Client", null) + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Socialize.Api.Modules.Workspaces.Data.Workspace", null) + .WithMany() + .HasForeignKey("WorkspaceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Channels.Data.Channel", b => + { + b.HasOne("Socialize.Api.Modules.Workspaces.Data.Workspace", null) + .WithMany() + .HasForeignKey("WorkspaceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Clients.Data.Client", b => + { + b.HasOne("Socialize.Api.Modules.Workspaces.Data.Workspace", null) + .WithMany() + .HasForeignKey("WorkspaceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Comments.Data.Comment", b => + { + b.HasOne("Socialize.Api.Modules.ContentItems.Data.ContentItem", null) + .WithMany() + .HasForeignKey("ContentItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Socialize.Api.Modules.Comments.Data.Comment", null) + .WithMany() + .HasForeignKey("ParentCommentId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Socialize.Api.Modules.Workspaces.Data.Workspace", null) + .WithMany() + .HasForeignKey("WorkspaceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.ContentItems.Data.ContentItem", b => + { + b.HasOne("Socialize.Api.Modules.Campaigns.Data.Campaign", null) + .WithMany() + .HasForeignKey("CampaignId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Socialize.Api.Modules.Clients.Data.Client", null) + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Socialize.Api.Modules.Workspaces.Data.Workspace", null) + .WithMany() + .HasForeignKey("WorkspaceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.ContentItems.Data.ContentItemActivityEntry", b => + { + b.HasOne("Socialize.Api.Modules.ContentItems.Data.ContentItem", null) + .WithMany() + .HasForeignKey("ContentItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Socialize.Api.Modules.Workspaces.Data.Workspace", null) + .WithMany() + .HasForeignKey("WorkspaceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.ContentItems.Data.ContentItemRevision", b => + { + b.HasOne("Socialize.Api.Modules.ContentItems.Data.ContentItem", null) + .WithMany() + .HasForeignKey("ContentItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Feedback.Data.FeedbackActivityEntry", b => + { + b.HasOne("Socialize.Api.Modules.Feedback.Data.FeedbackReport", "FeedbackReport") + .WithMany("ActivityEntries") + .HasForeignKey("FeedbackReportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("FeedbackReport"); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Feedback.Data.FeedbackComment", b => + { + b.HasOne("Socialize.Api.Modules.Feedback.Data.FeedbackReport", "FeedbackReport") + .WithMany("Comments") + .HasForeignKey("FeedbackReportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("FeedbackReport"); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Feedback.Data.FeedbackReport", b => + { + b.HasOne("Socialize.Api.Modules.Campaigns.Data.Campaign", null) + .WithMany() + .HasForeignKey("CampaignId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Socialize.Api.Modules.Clients.Data.Client", null) + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Socialize.Api.Modules.ContentItems.Data.ContentItem", null) + .WithMany() + .HasForeignKey("ContentItemId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Socialize.Api.Modules.Workspaces.Data.Workspace", null) + .WithMany() + .HasForeignKey("WorkspaceId") + .OnDelete(DeleteBehavior.SetNull); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Feedback.Data.FeedbackScreenshot", b => + { + b.HasOne("Socialize.Api.Modules.Feedback.Data.FeedbackReport", "FeedbackReport") + .WithOne("Screenshot") + .HasForeignKey("Socialize.Api.Modules.Feedback.Data.FeedbackScreenshot", "FeedbackReportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("FeedbackReport"); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Feedback.Data.FeedbackTag", b => + { + b.HasOne("Socialize.Api.Modules.Feedback.Data.FeedbackReport", "FeedbackReport") + .WithMany("Tags") + .HasForeignKey("FeedbackReportId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("FeedbackReport"); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Notifications.Data.NotificationEvent", b => + { + b.HasOne("Socialize.Api.Modules.ContentItems.Data.ContentItem", null) + .WithMany() + .HasForeignKey("ContentItemId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Socialize.Api.Modules.Workspaces.Data.Workspace", null) + .WithMany() + .HasForeignKey("WorkspaceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Organizations.Data.Organization", b => + { + b.HasOne("Socialize.Api.Modules.Organizations.Data.OrganizationMembershipTier", null) + .WithMany() + .HasForeignKey("MembershipTierId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Organizations.Data.OrganizationMembership", b => + { + b.HasOne("Socialize.Api.Modules.Organizations.Data.Organization", null) + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Organizations.Data.OrganizationMembershipTierTranslation", b => + { + b.HasOne("Socialize.Api.Modules.Organizations.Data.OrganizationMembershipTier", null) + .WithMany() + .HasForeignKey("MembershipTierId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.ReleaseCommunications.Data.ReleaseCommit", b => + { + b.HasOne("Socialize.Api.Modules.ReleaseCommunications.Data.ReleaseUpdate", "ReleaseUpdate") + .WithMany() + .HasForeignKey("ReleaseUpdateId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("ReleaseUpdate"); + }); + + modelBuilder.Entity("Socialize.Api.Modules.ReleaseCommunications.Data.ReleaseUpdateReadReceipt", b => + { + b.HasOne("Socialize.Api.Modules.ReleaseCommunications.Data.ReleaseUpdate", "ReleaseUpdate") + .WithMany("ReadReceipts") + .HasForeignKey("ReleaseUpdateId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ReleaseUpdate"); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Workspaces.Data.Workspace", b => + { + b.HasOne("Socialize.Api.Modules.Organizations.Data.Organization", null) + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Workspaces.Data.WorkspaceInvite", b => + { + b.HasOne("Socialize.Api.Modules.Workspaces.Data.Workspace", null) + .WithMany() + .HasForeignKey("WorkspaceId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Socialize.Api.Modules.Feedback.Data.FeedbackReport", b => + { + b.Navigation("ActivityEntries"); + + b.Navigation("Comments"); + + b.Navigation("Screenshot"); + + b.Navigation("Tags"); + }); + + modelBuilder.Entity("Socialize.Api.Modules.ReleaseCommunications.Data.ReleaseUpdate", b => + { + b.Navigation("ReadReceipts"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/backend/src/Socialize.Api/Migrations/20260508034902_ExpandReleaseUpdateDescriptions.cs b/backend/src/Socialize.Api/Migrations/20260508034902_ExpandReleaseUpdateDescriptions.cs new file mode 100644 index 00000000..1c8a0efd --- /dev/null +++ b/backend/src/Socialize.Api/Migrations/20260508034902_ExpandReleaseUpdateDescriptions.cs @@ -0,0 +1,62 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Socialize.Api.Migrations +{ + /// + internal partial class ExpandReleaseUpdateDescriptions : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + ArgumentNullException.ThrowIfNull(migrationBuilder); + + migrationBuilder.AlterColumn( + name: "SummaryFr", + table: "ReleaseUpdates", + type: "character varying(4000)", + maxLength: 4000, + nullable: false, + oldClrType: typeof(string), + oldType: "character varying(512)", + oldMaxLength: 512); + + migrationBuilder.AlterColumn( + name: "Summary", + table: "ReleaseUpdates", + type: "character varying(4000)", + maxLength: 4000, + nullable: false, + oldClrType: typeof(string), + oldType: "character varying(512)", + oldMaxLength: 512); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + ArgumentNullException.ThrowIfNull(migrationBuilder); + + migrationBuilder.AlterColumn( + name: "SummaryFr", + table: "ReleaseUpdates", + type: "character varying(512)", + maxLength: 512, + nullable: false, + oldClrType: typeof(string), + oldType: "character varying(4000)", + oldMaxLength: 4000); + + migrationBuilder.AlterColumn( + name: "Summary", + table: "ReleaseUpdates", + type: "character varying(512)", + maxLength: 512, + nullable: false, + oldClrType: typeof(string), + oldType: "character varying(4000)", + oldMaxLength: 4000); + } + } +} diff --git a/backend/src/Socialize.Api/Migrations/AppDbContextModelSnapshot.cs b/backend/src/Socialize.Api/Migrations/AppDbContextModelSnapshot.cs index 2182df43..0378e0c6 100644 --- a/backend/src/Socialize.Api/Migrations/AppDbContextModelSnapshot.cs +++ b/backend/src/Socialize.Api/Migrations/AppDbContextModelSnapshot.cs @@ -1980,28 +1980,6 @@ namespace Socialize.Api.Migrations b.Property("ArchivedAt") .HasColumnType("timestamp with time zone"); - b.Property("Audience") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)"); - - b.Property("Body") - .HasMaxLength(8000) - .HasColumnType("character varying(8000)"); - - b.Property("BuildVersion") - .HasMaxLength(128) - .HasColumnType("character varying(128)"); - - b.Property("Category") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)"); - - b.Property("CommitRange") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - b.Property("CreatedAt") .ValueGeneratedOnAdd() .HasColumnType("timestamp with time zone") @@ -2010,28 +1988,6 @@ namespace Socialize.Api.Migrations b.Property("CreatedByUserId") .HasColumnType("uuid"); - b.Property("DeploymentLabel") - .HasMaxLength(128) - .HasColumnType("character varying(128)"); - - b.Property("Importance") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)"); - - b.Property("ManualEmailAudience") - .HasMaxLength(64) - .HasColumnType("character varying(64)"); - - b.Property("ManualEmailRecipientCount") - .HasColumnType("integer"); - - b.Property("ManualEmailSentAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ManualEmailSentByUserId") - .HasColumnType("uuid"); - b.Property("PublishedAt") .HasColumnType("timestamp with time zone"); @@ -2042,21 +1998,29 @@ namespace Socialize.Api.Migrations b.Property("Summary") .IsRequired() - .HasMaxLength(512) - .HasColumnType("character varying(512)"); + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("SummaryFr") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); b.Property("Title") .IsRequired() .HasMaxLength(160) .HasColumnType("character varying(160)"); + b.Property("TitleFr") + .IsRequired() + .HasMaxLength(160) + .HasColumnType("character varying(160)"); + b.Property("UpdatedAt") .HasColumnType("timestamp with time zone"); b.HasKey("Id"); - b.HasIndex("Audience"); - b.HasIndex("CreatedByUserId"); b.HasIndex("PublishedAt"); diff --git a/backend/src/Socialize.Api/Modules/ReleaseCommunications/Contracts/ReleaseUpdateDtos.cs b/backend/src/Socialize.Api/Modules/ReleaseCommunications/Contracts/ReleaseUpdateDtos.cs index 9fcb08f3..50b40b0c 100644 --- a/backend/src/Socialize.Api/Modules/ReleaseCommunications/Contracts/ReleaseUpdateDtos.cs +++ b/backend/src/Socialize.Api/Modules/ReleaseCommunications/Contracts/ReleaseUpdateDtos.cs @@ -5,23 +5,16 @@ namespace Socialize.Api.Modules.ReleaseCommunications.Contracts; internal record ReleaseUpdateDto( Guid Id, string Title, - string Summary, - string? Body, - string Category, - string Importance, - string Audience, + string Description, + string TitleEn, + string DescriptionEn, + string TitleFr, + string DescriptionFr, string Status, - string? DeploymentLabel, - string? BuildVersion, - string? CommitRange, DateTimeOffset CreatedAt, DateTimeOffset UpdatedAt, DateTimeOffset? PublishedAt, DateTimeOffset? ArchivedAt, - Guid? ManualEmailSentByUserId, - DateTimeOffset? ManualEmailSentAt, - string? ManualEmailAudience, - int? ManualEmailRecipientCount, bool IsRead); internal record ReleaseCommitDto( @@ -40,16 +33,13 @@ internal record ReleaseCommitDto( DateTimeOffset ImportedAt, DateTimeOffset UpdatedAt); -internal record ReleaseCommitImportResultDto( - int ImportedCount, +internal record ReleaseCommitRefreshResultDto( + int CreatedCount, int UpdatedCount, int SkippedCount, IReadOnlyCollection Commits); -internal record ReleaseUpdateEmailSendResultDto( - int RecipientCount, - DateTimeOffset SentAt, - bool TestMode); +internal record ReleaseCommitBulkLinkResultDto(int LinkedCount); internal record ReleaseUpdateUnreadSummaryDto( int UnreadCount, @@ -64,22 +54,15 @@ internal static class ReleaseUpdateDtoMapper update.Id, update.Title, update.Summary, - update.Body, - ToDisplayString(update.Category), - update.Importance.ToString(), - update.Audience.ToString(), + update.Title, + update.Summary, + update.TitleFr, + update.SummaryFr, update.Status.ToString(), - update.DeploymentLabel, - update.BuildVersion, - update.CommitRange, update.CreatedAt, update.UpdatedAt, update.PublishedAt, update.ArchivedAt, - update.ManualEmailSentByUserId, - update.ManualEmailSentAt, - update.ManualEmailAudience, - update.ManualEmailRecipientCount, isRead); } @@ -102,8 +85,4 @@ internal static class ReleaseUpdateDtoMapper commit.UpdatedAt); } - private static string ToDisplayString(ReleaseUpdateCategory category) - { - return category == ReleaseUpdateCategory.BreakingChange ? "Breaking Change" : category.ToString(); - } } diff --git a/backend/src/Socialize.Api/Modules/ReleaseCommunications/Data/ReleaseCommunicationsModelConfiguration.cs b/backend/src/Socialize.Api/Modules/ReleaseCommunications/Data/ReleaseCommunicationsModelConfiguration.cs index f6ca0d65..3d54434a 100644 --- a/backend/src/Socialize.Api/Modules/ReleaseCommunications/Data/ReleaseCommunicationsModelConfiguration.cs +++ b/backend/src/Socialize.Api/Modules/ReleaseCommunications/Data/ReleaseCommunicationsModelConfiguration.cs @@ -11,19 +11,12 @@ internal static class ReleaseCommunicationsModelConfiguration releaseUpdate.ToTable("ReleaseUpdates"); releaseUpdate.HasKey(x => x.Id); releaseUpdate.Property(x => x.Title).HasMaxLength(160).IsRequired(); - releaseUpdate.Property(x => x.Summary).HasMaxLength(512).IsRequired(); - releaseUpdate.Property(x => x.Body).HasMaxLength(8000); - releaseUpdate.Property(x => x.Category).HasConversion().HasMaxLength(32).IsRequired(); - releaseUpdate.Property(x => x.Importance).HasConversion().HasMaxLength(32).IsRequired(); - releaseUpdate.Property(x => x.Audience).HasConversion().HasMaxLength(32).IsRequired(); + releaseUpdate.Property(x => x.Summary).HasMaxLength(4000).IsRequired(); + releaseUpdate.Property(x => x.TitleFr).HasMaxLength(160).IsRequired(); + releaseUpdate.Property(x => x.SummaryFr).HasMaxLength(4000).IsRequired(); releaseUpdate.Property(x => x.Status).HasConversion().HasMaxLength(32).IsRequired(); - releaseUpdate.Property(x => x.DeploymentLabel).HasMaxLength(128); - releaseUpdate.Property(x => x.BuildVersion).HasMaxLength(128); - releaseUpdate.Property(x => x.CommitRange).HasMaxLength(256); - releaseUpdate.Property(x => x.ManualEmailAudience).HasMaxLength(64); releaseUpdate.Property(x => x.CreatedAt).ValueGeneratedOnAdd().HasDefaultValueSql("CURRENT_TIMESTAMP"); releaseUpdate.HasIndex(x => x.Status); - releaseUpdate.HasIndex(x => x.Audience); releaseUpdate.HasIndex(x => x.PublishedAt); releaseUpdate.HasIndex(x => x.CreatedByUserId); }); diff --git a/backend/src/Socialize.Api/Modules/ReleaseCommunications/Data/ReleaseUpdate.cs b/backend/src/Socialize.Api/Modules/ReleaseCommunications/Data/ReleaseUpdate.cs index 0314ff52..ab9feb86 100644 --- a/backend/src/Socialize.Api/Modules/ReleaseCommunications/Data/ReleaseUpdate.cs +++ b/backend/src/Socialize.Api/Modules/ReleaseCommunications/Data/ReleaseUpdate.cs @@ -5,22 +5,13 @@ internal class ReleaseUpdate public Guid Id { get; set; } public string Title { get; set; } = string.Empty; public string Summary { get; set; } = string.Empty; - public string? Body { get; set; } - public ReleaseUpdateCategory Category { get; set; } - public ReleaseUpdateImportance Importance { get; set; } - public ReleaseUpdateAudience Audience { get; set; } + public string TitleFr { get; set; } = string.Empty; + public string SummaryFr { get; set; } = string.Empty; public ReleaseUpdateStatus Status { get; set; } - public string? DeploymentLabel { get; set; } - public string? BuildVersion { get; set; } - public string? CommitRange { get; set; } public Guid CreatedByUserId { get; set; } public DateTimeOffset CreatedAt { get; set; } public DateTimeOffset UpdatedAt { get; set; } public DateTimeOffset? PublishedAt { get; set; } public DateTimeOffset? ArchivedAt { get; set; } - public Guid? ManualEmailSentByUserId { get; set; } - public DateTimeOffset? ManualEmailSentAt { get; set; } - public string? ManualEmailAudience { get; set; } - public int? ManualEmailRecipientCount { get; set; } public ICollection ReadReceipts { get; } = new List(); } diff --git a/backend/src/Socialize.Api/Modules/ReleaseCommunications/Data/ReleaseUpdateAudience.cs b/backend/src/Socialize.Api/Modules/ReleaseCommunications/Data/ReleaseUpdateAudience.cs deleted file mode 100644 index e65f7427..00000000 --- a/backend/src/Socialize.Api/Modules/ReleaseCommunications/Data/ReleaseUpdateAudience.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Socialize.Api.Modules.ReleaseCommunications.Data; - -internal enum ReleaseUpdateAudience -{ - Everyone, - OrganizationOwners, - Developers, -} diff --git a/backend/src/Socialize.Api/Modules/ReleaseCommunications/Data/ReleaseUpdateCategory.cs b/backend/src/Socialize.Api/Modules/ReleaseCommunications/Data/ReleaseUpdateCategory.cs deleted file mode 100644 index 9f4bc845..00000000 --- a/backend/src/Socialize.Api/Modules/ReleaseCommunications/Data/ReleaseUpdateCategory.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Socialize.Api.Modules.ReleaseCommunications.Data; - -internal enum ReleaseUpdateCategory -{ - Feature, - Improvement, - Fix, - BreakingChange, -} diff --git a/backend/src/Socialize.Api/Modules/ReleaseCommunications/Data/ReleaseUpdateImportance.cs b/backend/src/Socialize.Api/Modules/ReleaseCommunications/Data/ReleaseUpdateImportance.cs deleted file mode 100644 index 3fb8ded4..00000000 --- a/backend/src/Socialize.Api/Modules/ReleaseCommunications/Data/ReleaseUpdateImportance.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Socialize.Api.Modules.ReleaseCommunications.Data; - -internal enum ReleaseUpdateImportance -{ - Normal, - Important, -} diff --git a/backend/src/Socialize.Api/Modules/ReleaseCommunications/Handlers/CreateDeveloperReleaseUpdate.cs b/backend/src/Socialize.Api/Modules/ReleaseCommunications/Handlers/CreateDeveloperReleaseUpdate.cs index 2aacd303..0c3148f7 100644 --- a/backend/src/Socialize.Api/Modules/ReleaseCommunications/Handlers/CreateDeveloperReleaseUpdate.cs +++ b/backend/src/Socialize.Api/Modules/ReleaseCommunications/Handlers/CreateDeveloperReleaseUpdate.cs @@ -4,35 +4,24 @@ using Socialize.Api.Infrastructure.Security; using Socialize.Api.Modules.Identity.Contracts; using Socialize.Api.Modules.ReleaseCommunications.Contracts; using Socialize.Api.Modules.ReleaseCommunications.Data; -using Socialize.Api.Modules.ReleaseCommunications.Services; namespace Socialize.Api.Modules.ReleaseCommunications.Handlers; internal record CreateDeveloperReleaseUpdateRequest( - string Title, - string Summary, - string? Body, - string Category, - string Importance, - string Audience, - string? DeploymentLabel, - string? BuildVersion, - string? CommitRange); + string TitleEn, + string DescriptionEn, + string TitleFr, + string DescriptionFr); internal class CreateDeveloperReleaseUpdateRequestValidator : Validator { public CreateDeveloperReleaseUpdateRequestValidator() { - RuleFor(x => x.Title).NotEmpty().MaximumLength(160); - RuleFor(x => x.Summary).NotEmpty().MaximumLength(512); - RuleFor(x => x.Body).MaximumLength(8000); - RuleFor(x => x.Category).NotEmpty().MaximumLength(32); - RuleFor(x => x.Importance).NotEmpty().MaximumLength(32); - RuleFor(x => x.Audience).NotEmpty().MaximumLength(32); - RuleFor(x => x.DeploymentLabel).MaximumLength(128); - RuleFor(x => x.BuildVersion).MaximumLength(128); - RuleFor(x => x.CommitRange).MaximumLength(256); + RuleFor(x => x.TitleEn).NotEmpty().MaximumLength(160); + RuleFor(x => x.DescriptionEn).NotEmpty().MaximumLength(4000); + RuleFor(x => x.TitleFr).NotEmpty().MaximumLength(160); + RuleFor(x => x.DescriptionFr).NotEmpty().MaximumLength(4000); } } @@ -48,26 +37,15 @@ internal class CreateDeveloperReleaseUpdateHandler(AppDbContext dbContext) public override async Task HandleAsync(CreateDeveloperReleaseUpdateRequest request, CancellationToken ct) { - if (!TryParseRequest(request, out ReleaseUpdateCategory category, out ReleaseUpdateImportance importance, out ReleaseUpdateAudience audience)) - { - await SendErrorsAsync(StatusCodes.Status400BadRequest, ct); - return; - } - DateTimeOffset now = DateTimeOffset.UtcNow; ReleaseUpdate update = new() { Id = Guid.NewGuid(), - Title = request.Title.Trim(), - Summary = request.Summary.Trim(), - Body = NormalizeOptional(request.Body), - Category = category, - Importance = importance, - Audience = audience, + Title = request.TitleEn.Trim(), + Summary = request.DescriptionEn.Trim(), + TitleFr = request.TitleFr.Trim(), + SummaryFr = request.DescriptionFr.Trim(), Status = ReleaseUpdateStatus.Draft, - DeploymentLabel = NormalizeOptional(request.DeploymentLabel), - BuildVersion = NormalizeOptional(request.BuildVersion), - CommitRange = NormalizeOptional(request.CommitRange), CreatedByUserId = User.GetUserId(), CreatedAt = now, UpdatedAt = now, @@ -78,38 +56,4 @@ internal class CreateDeveloperReleaseUpdateHandler(AppDbContext dbContext) await SendAsync(update.ToDto(false), StatusCodes.Status201Created, ct); } - - private bool TryParseRequest( - CreateDeveloperReleaseUpdateRequest request, - out ReleaseUpdateCategory category, - out ReleaseUpdateImportance importance, - out ReleaseUpdateAudience audience) - { - bool isValid = true; - if (!ReleaseUpdateRules.TryParseCategory(request.Category, out category)) - { - AddError(x => x.Category, "The selected release update category is not valid."); - isValid = false; - } - - if (!ReleaseUpdateRules.TryParseImportance(request.Importance, out importance)) - { - AddError(x => x.Importance, "The selected release update importance is not valid."); - isValid = false; - } - - if (!ReleaseUpdateRules.TryParseAudience(request.Audience, out audience)) - { - AddError(x => x.Audience, "The selected release update audience is not valid."); - isValid = false; - } - - return isValid; - } - - private static string? NormalizeOptional(string? value) - { - string? normalized = value?.Trim(); - return string.IsNullOrWhiteSpace(normalized) ? null : normalized; - } } diff --git a/backend/src/Socialize.Api/Modules/ReleaseCommunications/Handlers/GetUnreadReleaseUpdates.cs b/backend/src/Socialize.Api/Modules/ReleaseCommunications/Handlers/GetUnreadReleaseUpdates.cs index 998e348a..c177c988 100644 --- a/backend/src/Socialize.Api/Modules/ReleaseCommunications/Handlers/GetUnreadReleaseUpdates.cs +++ b/backend/src/Socialize.Api/Modules/ReleaseCommunications/Handlers/GetUnreadReleaseUpdates.cs @@ -20,11 +20,9 @@ internal class GetUnreadReleaseUpdatesHandler(AppDbContext dbContext) public override async Task HandleAsync(CancellationToken ct) { Guid userId = User.GetUserId(); - ReleaseUpdateAudienceContext audienceContext = - await ReleaseUpdateVisibility.GetAudienceContextAsync(dbContext, User, userId, ct); List unreadUpdates = await dbContext.ReleaseUpdates - .VisibleTo(audienceContext) + .VisibleToUsers() .Where(update => !dbContext.ReleaseUpdateReadReceipts.Any(receipt => receipt.ReleaseUpdateId == update.Id && receipt.UserId == userId)) @@ -35,7 +33,7 @@ internal class GetUnreadReleaseUpdatesHandler(AppDbContext dbContext) await SendOkAsync( new ReleaseUpdateUnreadSummaryDto( unreadUpdates.Count, - unreadUpdates.Count(update => update.Importance == ReleaseUpdateImportance.Important), + 0, unreadUpdates.Select(update => update.ToDto(false)).ToArray()), ct); } diff --git a/backend/src/Socialize.Api/Modules/ReleaseCommunications/Handlers/ImportDeveloperReleaseCommits.cs b/backend/src/Socialize.Api/Modules/ReleaseCommunications/Handlers/ImportDeveloperReleaseCommits.cs deleted file mode 100644 index 4ab96dfd..00000000 --- a/backend/src/Socialize.Api/Modules/ReleaseCommunications/Handlers/ImportDeveloperReleaseCommits.cs +++ /dev/null @@ -1,156 +0,0 @@ -using FastEndpoints; -using Microsoft.EntityFrameworkCore; -using System.Text.Json; -using Socialize.Api.Data; -using Socialize.Api.Modules.Identity.Contracts; -using Socialize.Api.Modules.ReleaseCommunications.Contracts; -using Socialize.Api.Modules.ReleaseCommunications.Data; -using Socialize.Api.Modules.ReleaseCommunications.Services; - -namespace Socialize.Api.Modules.ReleaseCommunications.Handlers; - -internal record ImportDeveloperReleaseCommitDto( - string Sha, - string? ShortSha, - string Subject, - string? AuthorName, - string? AuthorEmail, - DateTimeOffset? AuthoredAt, - DateTimeOffset? CommittedAt, - string? SourceBranch, - string? DeploymentLabel, - string? ExternalUrl); - -internal record ImportDeveloperReleaseCommitsRequest( - string? SinceSha, - string? UntilSha, - string? SourceBranch, - string? DeploymentLabel, - DateTimeOffset? Since, - DateTimeOffset? Until, - int? Limit, - IReadOnlyCollection? Commits); - -internal class ImportDeveloperReleaseCommitsHandler( - AppDbContext dbContext, - ReleaseCommitRepositoryImportService repositoryImportService) - : Endpoint -{ - public override void Configure() - { - Post("/api/developer/release-commits/import"); - Roles(KnownRoles.Developer); - Options(o => o.WithTags("Release Communications")); - } - - public override async Task HandleAsync(ImportDeveloperReleaseCommitsRequest request, CancellationToken ct) - { - IReadOnlyCollection requestedCommits; - if (request.Commits is { Count: > 0 }) - { - requestedCommits = request.Commits.Select(ToReleaseCommit).ToArray(); - } - else - { - try - { - ReleaseCommitRepositoryImportResult importResult = await repositoryImportService.FetchCommitsAsync(request, ct); - if (!importResult.IsSuccess) - { - AddError(importResult.ErrorMessage ?? "Repository commit import failed."); - await SendErrorsAsync(StatusCodes.Status400BadRequest, ct); - return; - } - - requestedCommits = importResult.Commits; - } - catch (HttpRequestException ex) - { - AddError(ex.Message); - await SendErrorsAsync(StatusCodes.Status400BadRequest, ct); - return; - } - catch (JsonException ex) - { - AddError(ex.Message); - await SendErrorsAsync(StatusCodes.Status400BadRequest, ct); - return; - } - } - - int imported = 0; - int updated = 0; - int skipped = 0; - List savedCommits = []; - foreach (ReleaseCommit requestedCommit in requestedCommits) - { - if (string.IsNullOrWhiteSpace(requestedCommit.Sha) || string.IsNullOrWhiteSpace(requestedCommit.Subject)) - { - skipped++; - continue; - } - - ReleaseCommit? existingCommit = await dbContext.ReleaseCommits.SingleOrDefaultAsync( - commit => commit.Sha == requestedCommit.Sha, - ct); - - if (existingCommit is null) - { - dbContext.ReleaseCommits.Add(requestedCommit); - savedCommits.Add(requestedCommit); - imported++; - continue; - } - - existingCommit.ShortSha = requestedCommit.ShortSha; - existingCommit.Subject = requestedCommit.Subject; - existingCommit.AuthorName = requestedCommit.AuthorName; - existingCommit.AuthorEmail = requestedCommit.AuthorEmail; - existingCommit.AuthoredAt = requestedCommit.AuthoredAt; - existingCommit.CommittedAt = requestedCommit.CommittedAt; - existingCommit.SourceBranch = requestedCommit.SourceBranch ?? existingCommit.SourceBranch; - existingCommit.DeploymentLabel = requestedCommit.DeploymentLabel ?? existingCommit.DeploymentLabel; - existingCommit.ExternalUrl = requestedCommit.ExternalUrl ?? existingCommit.ExternalUrl; - existingCommit.UpdatedAt = DateTimeOffset.UtcNow; - savedCommits.Add(existingCommit); - updated++; - } - - await dbContext.SaveChangesAsync(ct); - await SendOkAsync( - new ReleaseCommitImportResultDto(imported, updated, skipped, savedCommits.Select(commit => commit.ToDto()).ToArray()), - ct); - } - - private static ReleaseCommit ToReleaseCommit(ImportDeveloperReleaseCommitDto dto) - { - DateTimeOffset now = DateTimeOffset.UtcNow; - return new ReleaseCommit - { - Sha = dto.Sha.Trim(), - ShortSha = NormalizeOptional(dto.ShortSha) ?? dto.Sha.Trim()[..Math.Min(dto.Sha.Trim().Length, 12)], - Subject = dto.Subject.Trim(), - AuthorName = NormalizeOptional(dto.AuthorName), - AuthorEmail = NormalizeOptional(dto.AuthorEmail), - AuthoredAt = ToUtc(dto.AuthoredAt), - CommittedAt = ToUtc(dto.CommittedAt), - SourceBranch = NormalizeOptional(dto.SourceBranch), - DeploymentLabel = NormalizeOptional(dto.DeploymentLabel), - ExternalUrl = NormalizeOptional(dto.ExternalUrl), - CommunicationStatus = ReleaseCommitCommunicationStatus.Unreviewed, - ImportedAt = now, - UpdatedAt = now, - }; - } - - private static string? NormalizeOptional(string? value) - { - string? normalized = value?.Trim(); - return string.IsNullOrWhiteSpace(normalized) ? null : normalized; - } - - private static DateTimeOffset? ToUtc(DateTimeOffset? value) - { - return value?.ToUniversalTime(); - } -} diff --git a/backend/src/Socialize.Api/Modules/ReleaseCommunications/Handlers/ListReleaseUpdates.cs b/backend/src/Socialize.Api/Modules/ReleaseCommunications/Handlers/ListReleaseUpdates.cs index bd95f681..21af356a 100644 --- a/backend/src/Socialize.Api/Modules/ReleaseCommunications/Handlers/ListReleaseUpdates.cs +++ b/backend/src/Socialize.Api/Modules/ReleaseCommunications/Handlers/ListReleaseUpdates.cs @@ -20,11 +20,9 @@ internal class ListReleaseUpdatesHandler(AppDbContext dbContext) public override async Task HandleAsync(CancellationToken ct) { Guid userId = User.GetUserId(); - ReleaseUpdateAudienceContext audienceContext = - await ReleaseUpdateVisibility.GetAudienceContextAsync(dbContext, User, userId, ct); List updates = await dbContext.ReleaseUpdates - .VisibleTo(audienceContext) + .VisibleToUsers() .OrderByDescending(update => update.PublishedAt) .ThenByDescending(update => update.CreatedAt) .ToListAsync(ct); diff --git a/backend/src/Socialize.Api/Modules/ReleaseCommunications/Handlers/MarkAllReleaseUpdatesRead.cs b/backend/src/Socialize.Api/Modules/ReleaseCommunications/Handlers/MarkAllReleaseUpdatesRead.cs index c3bc4451..3d41565f 100644 --- a/backend/src/Socialize.Api/Modules/ReleaseCommunications/Handlers/MarkAllReleaseUpdatesRead.cs +++ b/backend/src/Socialize.Api/Modules/ReleaseCommunications/Handlers/MarkAllReleaseUpdatesRead.cs @@ -19,11 +19,9 @@ internal class MarkAllReleaseUpdatesReadHandler(AppDbContext dbContext) public override async Task HandleAsync(CancellationToken ct) { Guid userId = User.GetUserId(); - ReleaseUpdateAudienceContext audienceContext = - await ReleaseUpdateVisibility.GetAudienceContextAsync(dbContext, User, userId, ct); List visibleUpdateIds = await dbContext.ReleaseUpdates - .VisibleTo(audienceContext) + .VisibleToUsers() .Select(update => update.Id) .ToListAsync(ct); diff --git a/backend/src/Socialize.Api/Modules/ReleaseCommunications/Handlers/MarkReleaseUpdateRead.cs b/backend/src/Socialize.Api/Modules/ReleaseCommunications/Handlers/MarkReleaseUpdateRead.cs index fed5dc27..9cec64c3 100644 --- a/backend/src/Socialize.Api/Modules/ReleaseCommunications/Handlers/MarkReleaseUpdateRead.cs +++ b/backend/src/Socialize.Api/Modules/ReleaseCommunications/Handlers/MarkReleaseUpdateRead.cs @@ -20,11 +20,9 @@ internal class MarkReleaseUpdateReadHandler(AppDbContext dbContext) { Guid id = Route("id"); Guid userId = User.GetUserId(); - ReleaseUpdateAudienceContext audienceContext = - await ReleaseUpdateVisibility.GetAudienceContextAsync(dbContext, User, userId, ct); bool canReadUpdate = await dbContext.ReleaseUpdates - .VisibleTo(audienceContext) + .VisibleToUsers() .AnyAsync(update => update.Id == id, ct); if (!canReadUpdate) diff --git a/backend/src/Socialize.Api/Modules/ReleaseCommunications/Handlers/RefreshDeveloperReleaseCommits.cs b/backend/src/Socialize.Api/Modules/ReleaseCommunications/Handlers/RefreshDeveloperReleaseCommits.cs new file mode 100644 index 00000000..5be25d75 --- /dev/null +++ b/backend/src/Socialize.Api/Modules/ReleaseCommunications/Handlers/RefreshDeveloperReleaseCommits.cs @@ -0,0 +1,95 @@ +using FastEndpoints; +using Microsoft.EntityFrameworkCore; +using System.Text.Json; +using Socialize.Api.Data; +using Socialize.Api.Modules.Identity.Contracts; +using Socialize.Api.Modules.ReleaseCommunications.Contracts; +using Socialize.Api.Modules.ReleaseCommunications.Data; +using Socialize.Api.Modules.ReleaseCommunications.Services; + +namespace Socialize.Api.Modules.ReleaseCommunications.Handlers; + +internal class RefreshDeveloperReleaseCommitsHandler( + AppDbContext dbContext, + ReleaseCommitRepositoryRefreshService repositoryRefreshService) + : EndpointWithoutRequest +{ + public override void Configure() + { + Post("/api/developer/release-commits/refresh"); + Roles(KnownRoles.Developer); + Options(o => o.WithTags("Release Communications")); + } + + public override async Task HandleAsync(CancellationToken ct) + { + IReadOnlyCollection requestedCommits; + try + { + ReleaseCommitRepositoryRefreshResult refreshResult = await repositoryRefreshService.FetchCommitsAsync(ct); + if (!refreshResult.IsSuccess) + { + AddError(refreshResult.ErrorMessage ?? "Repository commit refresh failed."); + await SendErrorsAsync(StatusCodes.Status400BadRequest, ct); + return; + } + + requestedCommits = refreshResult.Commits; + } + catch (HttpRequestException ex) + { + AddError(ex.Message); + await SendErrorsAsync(StatusCodes.Status400BadRequest, ct); + return; + } + catch (JsonException ex) + { + AddError(ex.Message); + await SendErrorsAsync(StatusCodes.Status400BadRequest, ct); + return; + } + + int created = 0; + int updated = 0; + int skipped = 0; + List savedCommits = []; + foreach (ReleaseCommit requestedCommit in requestedCommits) + { + if (string.IsNullOrWhiteSpace(requestedCommit.Sha) || string.IsNullOrWhiteSpace(requestedCommit.Subject)) + { + skipped++; + continue; + } + + ReleaseCommit? existingCommit = await dbContext.ReleaseCommits.SingleOrDefaultAsync( + commit => commit.Sha == requestedCommit.Sha, + ct); + + if (existingCommit is null) + { + dbContext.ReleaseCommits.Add(requestedCommit); + savedCommits.Add(requestedCommit); + created++; + continue; + } + + existingCommit.ShortSha = requestedCommit.ShortSha; + existingCommit.Subject = requestedCommit.Subject; + existingCommit.AuthorName = requestedCommit.AuthorName; + existingCommit.AuthorEmail = requestedCommit.AuthorEmail; + existingCommit.AuthoredAt = requestedCommit.AuthoredAt; + existingCommit.CommittedAt = requestedCommit.CommittedAt; + existingCommit.SourceBranch = requestedCommit.SourceBranch ?? existingCommit.SourceBranch; + existingCommit.DeploymentLabel = requestedCommit.DeploymentLabel ?? existingCommit.DeploymentLabel; + existingCommit.ExternalUrl = requestedCommit.ExternalUrl ?? existingCommit.ExternalUrl; + existingCommit.UpdatedAt = DateTimeOffset.UtcNow; + savedCommits.Add(existingCommit); + updated++; + } + + await dbContext.SaveChangesAsync(ct); + await SendOkAsync( + new ReleaseCommitRefreshResultDto(created, updated, skipped, savedCommits.Select(commit => commit.ToDto()).ToArray()), + ct); + } +} diff --git a/backend/src/Socialize.Api/Modules/ReleaseCommunications/Handlers/SendDeveloperReleaseUpdateEmail.cs b/backend/src/Socialize.Api/Modules/ReleaseCommunications/Handlers/SendDeveloperReleaseUpdateEmail.cs deleted file mode 100644 index 1695d7ab..00000000 --- a/backend/src/Socialize.Api/Modules/ReleaseCommunications/Handlers/SendDeveloperReleaseUpdateEmail.cs +++ /dev/null @@ -1,55 +0,0 @@ -using FastEndpoints; -using Microsoft.EntityFrameworkCore; -using Socialize.Api.Data; -using Socialize.Api.Infrastructure.Security; -using Socialize.Api.Modules.Identity.Contracts; -using Socialize.Api.Modules.ReleaseCommunications.Contracts; -using Socialize.Api.Modules.ReleaseCommunications.Data; -using Socialize.Api.Modules.ReleaseCommunications.Services; - -namespace Socialize.Api.Modules.ReleaseCommunications.Handlers; - -internal record SendDeveloperReleaseUpdateEmailRequest( - bool TestMode, - bool ConfirmResend); - -internal class SendDeveloperReleaseUpdateEmailHandler( - AppDbContext dbContext, - ReleaseUpdateEmailService emailService) - : Endpoint -{ - public override void Configure() - { - Post("/api/developer/release-updates/{id}/send-email"); - Roles(KnownRoles.Developer); - Options(o => o.WithTags("Release Communications")); - } - - public override async Task HandleAsync(SendDeveloperReleaseUpdateEmailRequest request, CancellationToken ct) - { - Guid id = Route("id"); - ReleaseUpdate? update = await dbContext.ReleaseUpdates.SingleOrDefaultAsync(candidate => candidate.Id == id, ct); - if (update is null) - { - await SendNotFoundAsync(ct); - return; - } - - try - { - ReleaseUpdateEmailSendResultDto result = await emailService.SendManualUpdateEmailAsync( - update, - User.GetUserId(), - request.TestMode, - request.ConfirmResend, - ct); - await dbContext.SaveChangesAsync(ct); - await SendOkAsync(result, ct); - } - catch (InvalidOperationException ex) - { - AddError(ex.Message); - await SendErrorsAsync(StatusCodes.Status400BadRequest, ct); - } - } -} diff --git a/backend/src/Socialize.Api/Modules/ReleaseCommunications/Handlers/UpdateDeveloperReleaseCommitStatus.cs b/backend/src/Socialize.Api/Modules/ReleaseCommunications/Handlers/UpdateDeveloperReleaseCommitStatus.cs index dc081dd4..dfb549c9 100644 --- a/backend/src/Socialize.Api/Modules/ReleaseCommunications/Handlers/UpdateDeveloperReleaseCommitStatus.cs +++ b/backend/src/Socialize.Api/Modules/ReleaseCommunications/Handlers/UpdateDeveloperReleaseCommitStatus.cs @@ -9,6 +9,8 @@ namespace Socialize.Api.Modules.ReleaseCommunications.Handlers; internal record LinkDeveloperReleaseCommitRequest(Guid ReleaseUpdateId); +internal record LinkFirstReleaseCommitsRequest(Guid ReleaseUpdateId); + internal abstract class ReleaseCommitStatusEndpoint(AppDbContext dbContext) : EndpointWithoutRequest { @@ -67,6 +69,70 @@ internal class LinkDeveloperReleaseCommitHandler(AppDbContext dbContext) } } +internal class LinkFirstReleaseCommitsHandler(AppDbContext dbContext) + : Endpoint +{ + public override void Configure() + { + Post("/api/developer/release-commits/{sha}/link-first-release"); + Roles(KnownRoles.Developer); + Options(o => o.WithTags("Release Communications")); + } + + public override async Task HandleAsync(LinkFirstReleaseCommitsRequest request, CancellationToken ct) + { + string? sha = Route("sha"); + if (string.IsNullOrWhiteSpace(sha)) + { + await SendNotFoundAsync(ct); + return; + } + + bool releaseUpdateExists = await dbContext.ReleaseUpdates + .AnyAsync(update => update.Id == request.ReleaseUpdateId, ct); + ReleaseCommit? anchorCommit = await dbContext.ReleaseCommits + .SingleOrDefaultAsync(commit => commit.Sha == sha, ct); + + if (!releaseUpdateExists || anchorCommit is null) + { + await SendNotFoundAsync(ct); + return; + } + + if (anchorCommit.ReleaseUpdateId is not null || + anchorCommit.CommunicationStatus != ReleaseCommitCommunicationStatus.Unreviewed) + { + AddError("The selected first release commit must be unlinked and unreviewed."); + await SendErrorsAsync(StatusCodes.Status400BadRequest, ct); + return; + } + + DateTimeOffset anchorDate = CommitDate(anchorCommit); + List commits = await dbContext.ReleaseCommits + .Where(commit => + commit.ReleaseUpdateId == null && + commit.CommunicationStatus == ReleaseCommitCommunicationStatus.Unreviewed && + (commit.CommittedAt ?? commit.AuthoredAt ?? commit.ImportedAt) <= anchorDate) + .ToListAsync(ct); + + DateTimeOffset now = DateTimeOffset.UtcNow; + foreach (ReleaseCommit commit in commits) + { + commit.ReleaseUpdateId = request.ReleaseUpdateId; + commit.CommunicationStatus = ReleaseCommitCommunicationStatus.Linked; + commit.UpdatedAt = now; + } + + await dbContext.SaveChangesAsync(ct); + await SendOkAsync(new ReleaseCommitBulkLinkResultDto(commits.Count), ct); + } + + private static DateTimeOffset CommitDate(ReleaseCommit commit) + { + return commit.CommittedAt ?? commit.AuthoredAt ?? commit.ImportedAt; + } +} + internal class UnlinkDeveloperReleaseCommitHandler(AppDbContext dbContext) : ReleaseCommitStatusEndpoint(dbContext) { diff --git a/backend/src/Socialize.Api/Modules/ReleaseCommunications/Handlers/UpdateDeveloperReleaseUpdate.cs b/backend/src/Socialize.Api/Modules/ReleaseCommunications/Handlers/UpdateDeveloperReleaseUpdate.cs index 3bfa2ae3..3bd37011 100644 --- a/backend/src/Socialize.Api/Modules/ReleaseCommunications/Handlers/UpdateDeveloperReleaseUpdate.cs +++ b/backend/src/Socialize.Api/Modules/ReleaseCommunications/Handlers/UpdateDeveloperReleaseUpdate.cs @@ -4,35 +4,24 @@ using Socialize.Api.Data; using Socialize.Api.Modules.Identity.Contracts; using Socialize.Api.Modules.ReleaseCommunications.Contracts; using Socialize.Api.Modules.ReleaseCommunications.Data; -using Socialize.Api.Modules.ReleaseCommunications.Services; namespace Socialize.Api.Modules.ReleaseCommunications.Handlers; internal record UpdateDeveloperReleaseUpdateRequest( - string Title, - string Summary, - string? Body, - string Category, - string Importance, - string Audience, - string? DeploymentLabel, - string? BuildVersion, - string? CommitRange); + string TitleEn, + string DescriptionEn, + string TitleFr, + string DescriptionFr); internal class UpdateDeveloperReleaseUpdateRequestValidator : Validator { public UpdateDeveloperReleaseUpdateRequestValidator() { - RuleFor(x => x.Title).NotEmpty().MaximumLength(160); - RuleFor(x => x.Summary).NotEmpty().MaximumLength(512); - RuleFor(x => x.Body).MaximumLength(8000); - RuleFor(x => x.Category).NotEmpty().MaximumLength(32); - RuleFor(x => x.Importance).NotEmpty().MaximumLength(32); - RuleFor(x => x.Audience).NotEmpty().MaximumLength(32); - RuleFor(x => x.DeploymentLabel).MaximumLength(128); - RuleFor(x => x.BuildVersion).MaximumLength(128); - RuleFor(x => x.CommitRange).MaximumLength(256); + RuleFor(x => x.TitleEn).NotEmpty().MaximumLength(160); + RuleFor(x => x.DescriptionEn).NotEmpty().MaximumLength(4000); + RuleFor(x => x.TitleFr).NotEmpty().MaximumLength(160); + RuleFor(x => x.DescriptionFr).NotEmpty().MaximumLength(4000); } } @@ -63,58 +52,13 @@ internal class UpdateDeveloperReleaseUpdateHandler(AppDbContext dbContext) return; } - if (!TryParseRequest(request, out ReleaseUpdateCategory category, out ReleaseUpdateImportance importance, out ReleaseUpdateAudience audience)) - { - await SendErrorsAsync(StatusCodes.Status400BadRequest, ct); - return; - } - - update.Title = request.Title.Trim(); - update.Summary = request.Summary.Trim(); - update.Body = NormalizeOptional(request.Body); - update.Category = category; - update.Importance = importance; - update.Audience = audience; - update.DeploymentLabel = NormalizeOptional(request.DeploymentLabel); - update.BuildVersion = NormalizeOptional(request.BuildVersion); - update.CommitRange = NormalizeOptional(request.CommitRange); + update.Title = request.TitleEn.Trim(); + update.Summary = request.DescriptionEn.Trim(); + update.TitleFr = request.TitleFr.Trim(); + update.SummaryFr = request.DescriptionFr.Trim(); update.UpdatedAt = DateTimeOffset.UtcNow; await dbContext.SaveChangesAsync(ct); await SendOkAsync(update.ToDto(false), ct); } - - private bool TryParseRequest( - UpdateDeveloperReleaseUpdateRequest request, - out ReleaseUpdateCategory category, - out ReleaseUpdateImportance importance, - out ReleaseUpdateAudience audience) - { - bool isValid = true; - if (!ReleaseUpdateRules.TryParseCategory(request.Category, out category)) - { - AddError(x => x.Category, "The selected release update category is not valid."); - isValid = false; - } - - if (!ReleaseUpdateRules.TryParseImportance(request.Importance, out importance)) - { - AddError(x => x.Importance, "The selected release update importance is not valid."); - isValid = false; - } - - if (!ReleaseUpdateRules.TryParseAudience(request.Audience, out audience)) - { - AddError(x => x.Audience, "The selected release update audience is not valid."); - isValid = false; - } - - return isValid; - } - - private static string? NormalizeOptional(string? value) - { - string? normalized = value?.Trim(); - return string.IsNullOrWhiteSpace(normalized) ? null : normalized; - } } diff --git a/backend/src/Socialize.Api/Modules/ReleaseCommunications/ModuleRegistration.cs b/backend/src/Socialize.Api/Modules/ReleaseCommunications/ModuleRegistration.cs index cf337427..450c8f42 100644 --- a/backend/src/Socialize.Api/Modules/ReleaseCommunications/ModuleRegistration.cs +++ b/backend/src/Socialize.Api/Modules/ReleaseCommunications/ModuleRegistration.cs @@ -12,7 +12,7 @@ internal static class ModuleRegistration builder.Services.Configure( builder.Configuration.GetSection(ReleaseCommunicationRepositoryOptions.SectionName)); builder.Services.AddScoped(); - builder.Services.AddScoped(); + builder.Services.AddScoped(); builder.Services.AddHostedService(); return builder; diff --git a/backend/src/Socialize.Api/Modules/ReleaseCommunications/Services/ReleaseCommitRepositoryImportService.cs b/backend/src/Socialize.Api/Modules/ReleaseCommunications/Services/ReleaseCommitRepositoryRefreshService.cs similarity index 74% rename from backend/src/Socialize.Api/Modules/ReleaseCommunications/Services/ReleaseCommitRepositoryImportService.cs rename to backend/src/Socialize.Api/Modules/ReleaseCommunications/Services/ReleaseCommitRepositoryRefreshService.cs index 24f80d59..5d63bfe1 100644 --- a/backend/src/Socialize.Api/Modules/ReleaseCommunications/Services/ReleaseCommitRepositoryImportService.cs +++ b/backend/src/Socialize.Api/Modules/ReleaseCommunications/Services/ReleaseCommitRepositoryRefreshService.cs @@ -5,46 +5,41 @@ using System.Text.Json; using Microsoft.Extensions.Options; using Socialize.Api.Modules.ReleaseCommunications.Configuration; using Socialize.Api.Modules.ReleaseCommunications.Data; -using Socialize.Api.Modules.ReleaseCommunications.Handlers; namespace Socialize.Api.Modules.ReleaseCommunications.Services; -internal sealed record ReleaseCommitRepositoryImportResult( +internal sealed record ReleaseCommitRepositoryRefreshResult( IReadOnlyCollection Commits, string? ErrorMessage) { public bool IsSuccess => ErrorMessage is null; - public static ReleaseCommitRepositoryImportResult Success(IReadOnlyCollection commits) + public static ReleaseCommitRepositoryRefreshResult Success(IReadOnlyCollection commits) { - return new ReleaseCommitRepositoryImportResult(commits, null); + return new ReleaseCommitRepositoryRefreshResult(commits, null); } - public static ReleaseCommitRepositoryImportResult Failure(string errorMessage) + public static ReleaseCommitRepositoryRefreshResult Failure(string errorMessage) { - return new ReleaseCommitRepositoryImportResult([], errorMessage); + return new ReleaseCommitRepositoryRefreshResult([], errorMessage); } } -internal sealed class ReleaseCommitRepositoryImportService( +internal sealed class ReleaseCommitRepositoryRefreshService( IHttpClientFactory httpClientFactory, IOptionsSnapshot repositoryOptions) { private const int DefaultLimit = 50; - private const int MaxLimit = 100; - public async Task FetchCommitsAsync( - ImportDeveloperReleaseCommitsRequest request, + public async Task FetchCommitsAsync( CancellationToken ct) { ReleaseCommunicationRepositoryOptions options = repositoryOptions.Value; if (!TryBuildApiTarget(options.RepositoryUrl, out RepositoryApiTarget target, out string? targetError)) { - return ReleaseCommitRepositoryImportResult.Failure(targetError ?? "Repository configuration is not valid."); + return ReleaseCommitRepositoryRefreshResult.Failure(targetError ?? "Repository configuration is not valid."); } - int limit = Math.Clamp(request.Limit ?? DefaultLimit, 1, MaxLimit); - using HttpClient httpClient = httpClientFactory.CreateClient(); httpClient.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("Socialize", "1.0")); if (!string.IsNullOrWhiteSpace(options.AccessToken)) @@ -52,11 +47,11 @@ internal sealed class ReleaseCommitRepositoryImportService( httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("token", options.AccessToken.Trim()); } - using HttpResponseMessage response = await httpClient.GetAsync(BuildRequestUri(target, request, limit), ct); + using HttpResponseMessage response = await httpClient.GetAsync(BuildRequestUri(target), ct); if (!response.IsSuccessStatusCode) { - return ReleaseCommitRepositoryImportResult.Failure( - $"Repository commit import failed with HTTP {(int)response.StatusCode} ({response.ReasonPhrase})."); + return ReleaseCommitRepositoryRefreshResult.Failure( + $"Repository commit refresh failed with HTTP {(int)response.StatusCode} ({response.ReasonPhrase})."); } await using Stream stream = await response.Content.ReadAsStreamAsync(ct); @@ -73,56 +68,31 @@ internal sealed class ReleaseCommitRepositoryImportService( } else { - return ReleaseCommitRepositoryImportResult.Failure("Repository API response did not include a commit list."); + return ReleaseCommitRepositoryRefreshResult.Failure("Repository API response did not include a commit list."); } DateTimeOffset now = DateTimeOffset.UtcNow; List commits = []; foreach (JsonElement commitElement in commitsElement.EnumerateArray()) { - ReleaseCommit? commit = ToReleaseCommit(commitElement, request, now); + ReleaseCommit? commit = ToReleaseCommit(commitElement, now); if (commit is not null) { commits.Add(commit); } } - return ReleaseCommitRepositoryImportResult.Success(commits); + return ReleaseCommitRepositoryRefreshResult.Success(commits); } - private static Uri BuildRequestUri( - RepositoryApiTarget target, - ImportDeveloperReleaseCommitsRequest request, - int limit) + private static Uri BuildRequestUri(RepositoryApiTarget target) { - if (!string.IsNullOrWhiteSpace(request.SinceSha) && !string.IsNullOrWhiteSpace(request.UntilSha)) - { - string baseHead = $"{request.SinceSha.Trim()}...{request.UntilSha.Trim()}"; - return new Uri($"{target.ApiBaseUri}/compare/{Uri.EscapeDataString(baseHead)}"); - } - Dictionary query = new(StringComparer.Ordinal) { - ["limit"] = limit.ToString(CultureInfo.InvariantCulture), + ["limit"] = DefaultLimit.ToString(CultureInfo.InvariantCulture), ["page"] = "1", }; - string? sha = NormalizeOptional(request.UntilSha) ?? NormalizeOptional(request.SourceBranch); - if (sha is not null) - { - query["sha"] = sha; - } - - if (request.Since.HasValue) - { - query["since"] = request.Since.Value.UtcDateTime.ToString("O", CultureInfo.InvariantCulture); - } - - if (request.Until.HasValue) - { - query["until"] = request.Until.Value.UtcDateTime.ToString("O", CultureInfo.InvariantCulture); - } - string queryString = string.Join( "&", query.Select(pair => $"{WebUtility.UrlEncode(pair.Key)}={WebUtility.UrlEncode(pair.Value)}")); @@ -140,7 +110,7 @@ internal sealed class ReleaseCommitRepositoryImportService( if (string.IsNullOrWhiteSpace(repositoryUrl)) { - errorMessage = "ReleaseCommunications:Repository:RepositoryUrl is required before repository import can be used."; + errorMessage = "ReleaseCommunications:Repository:RepositoryUrl is required before repository refresh can be used."; return false; } @@ -176,7 +146,6 @@ internal sealed class ReleaseCommitRepositoryImportService( private static ReleaseCommit? ToReleaseCommit( JsonElement commitElement, - ImportDeveloperReleaseCommitsRequest request, DateTimeOffset now) { string? sha = GetString(commitElement, "sha") ?? GetString(commitElement, "id"); @@ -211,8 +180,8 @@ internal sealed class ReleaseCommitRepositoryImportService( AuthorEmail = authorElement.HasValue ? NormalizeOptional(GetString(authorElement.Value, "email")) : null, AuthoredAt = authorElement.HasValue ? GetUtcDateTimeOffset(authorElement.Value, "date") : null, CommittedAt = committerElement.HasValue ? GetUtcDateTimeOffset(committerElement.Value, "date") : null, - SourceBranch = NormalizeOptional(request.SourceBranch), - DeploymentLabel = NormalizeOptional(request.DeploymentLabel), + SourceBranch = null, + DeploymentLabel = null, ExternalUrl = NormalizeOptional(GetString(commitElement, "html_url") ?? GetString(commitElement, "url")), CommunicationStatus = ReleaseCommitCommunicationStatus.Unreviewed, ImportedAt = now, diff --git a/backend/src/Socialize.Api/Modules/ReleaseCommunications/Services/ReleaseUpdateEmailService.cs b/backend/src/Socialize.Api/Modules/ReleaseCommunications/Services/ReleaseUpdateEmailService.cs index 219879d2..9825c100 100644 --- a/backend/src/Socialize.Api/Modules/ReleaseCommunications/Services/ReleaseUpdateEmailService.cs +++ b/backend/src/Socialize.Api/Modules/ReleaseCommunications/Services/ReleaseUpdateEmailService.cs @@ -1,15 +1,11 @@ using System.Net; -using System.Security.Claims; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; using Socialize.Api.Data; using Socialize.Api.Infrastructure.Configuration; using Socialize.Api.Infrastructure.Emailer.Contracts; -using Socialize.Api.Modules.Identity.Contracts; using Socialize.Api.Modules.Identity.Data; -using Socialize.Api.Modules.Organizations.Services; -using Socialize.Api.Modules.ReleaseCommunications.Contracts; using Socialize.Api.Modules.ReleaseCommunications.Data; namespace Socialize.Api.Modules.ReleaseCommunications.Services; @@ -20,48 +16,6 @@ internal class ReleaseUpdateEmailService( IEmailSender emailSender, IOptionsSnapshot websiteOptions) { - public async Task SendManualUpdateEmailAsync( - ReleaseUpdate update, - Guid senderUserId, - bool testMode, - bool confirmResend, - CancellationToken ct) - { - if (update.Status != ReleaseUpdateStatus.Published) - { - throw new InvalidOperationException("Only published release updates can be emailed."); - } - - if (!testMode && update.ManualEmailSentAt.HasValue && !confirmResend) - { - throw new InvalidOperationException("This release update was already emailed. Confirm resend to send it again."); - } - - IReadOnlyCollection recipients = testMode - ? await GetTestRecipientsAsync(senderUserId, ct) - : await GetAudienceRecipientsAsync(update.Audience, ct); - - DateTimeOffset now = DateTimeOffset.UtcNow; - foreach (User recipient in recipients.Where(recipient => !string.IsNullOrWhiteSpace(recipient.Email))) - { - await emailSender.SendEmailAsync( - recipient.Email!, - $"What's new in Socialize: {update.Title}", - BuildSingleUpdateEmail(update)); - } - - if (!testMode) - { - update.ManualEmailSentByUserId = senderUserId; - update.ManualEmailSentAt = now; - update.ManualEmailAudience = update.Audience.ToString(); - update.ManualEmailRecipientCount = recipients.Count; - update.UpdatedAt = now; - } - - return new ReleaseUpdateEmailSendResultDto(recipients.Count, now, testMode); - } - public async Task SendDueDigestEmailsAsync( TimeSpan inactiveThreshold, TimeSpan sendInterval, @@ -71,7 +25,7 @@ internal class ReleaseUpdateEmailService( DateTimeOffset inactiveBefore = now.Subtract(inactiveThreshold); DateTimeOffset lastSentBefore = now.Subtract(sendInterval); - List ownerUsers = await GetAudienceRecipientsAsync(ReleaseUpdateAudience.OrganizationOwners, ct); + List ownerUsers = await GetReleaseNoteRecipientsAsync(ct); int sentCount = 0; foreach (User user in ownerUsers) { @@ -91,14 +45,8 @@ internal class ReleaseUpdateEmailService( continue; } - ReleaseUpdateAudienceContext audienceContext = await ReleaseUpdateVisibility.GetAudienceContextAsync( - dbContext, - new ClaimsPrincipal(new ClaimsIdentity()), - user.Id, - ct); - List unreadUpdates = await dbContext.ReleaseUpdates - .VisibleTo(audienceContext) + .VisibleToUsers() .Where(update => !dbContext.ReleaseUpdateReadReceipts.Any(receipt => receipt.ReleaseUpdateId == update.Id && receipt.UserId == user.Id)) @@ -130,48 +78,12 @@ internal class ReleaseUpdateEmailService( return sentCount; } - private async Task> GetTestRecipientsAsync(Guid senderUserId, CancellationToken ct) + private async Task> GetReleaseNoteRecipientsAsync(CancellationToken ct) { - User? sender = await userManager.Users.SingleOrDefaultAsync(user => user.Id == senderUserId, ct); - return sender is null ? [] : [sender]; - } - - private async Task> GetAudienceRecipientsAsync(ReleaseUpdateAudience audience, CancellationToken ct) - { - IQueryable query = userManager.Users.Where(user => user.EmailConfirmed && user.Email != null); - - if (audience == ReleaseUpdateAudience.Developers) - { - IList developers = await userManager.GetUsersInRoleAsync(KnownRoles.Developer); - return developers.Where(user => user.EmailConfirmed && !string.IsNullOrWhiteSpace(user.Email)).ToList(); - } - - if (audience == ReleaseUpdateAudience.OrganizationOwners) - { - Guid[] ownerUserIds = await dbContext.Organizations - .Select(organization => organization.OwnerUserId) - .Concat(dbContext.OrganizationMemberships - .Where(membership => membership.Role == OrganizationRoles.Owner) - .Select(membership => membership.UserId)) - .Distinct() - .ToArrayAsync(ct); - - query = query.Where(user => ownerUserIds.Contains(user.Id)); - } - - return await query.OrderBy(user => user.Email).ToListAsync(ct); - } - - private string BuildSingleUpdateEmail(ReleaseUpdate update) - { - string updateUrl = $"{websiteOptions.Value.FrontendBaseUrl.TrimEnd('/')}/app/updates?updateId={update.Id}"; - return $""" -

{HtmlEncode(update.Title)}

-

{HtmlEncode(update.Category.ToString())}

-

{HtmlEncode(update.Summary)}

- {FormatBody(update.Body)} -

Open What's New

- """; + return await userManager.Users + .Where(user => user.EmailConfirmed && user.Email != null) + .OrderBy(user => user.Email) + .ToListAsync(ct); } private string BuildDigestEmail(IReadOnlyCollection updates) @@ -179,7 +91,12 @@ internal class ReleaseUpdateEmailService( string updateUrl = $"{websiteOptions.Value.FrontendBaseUrl.TrimEnd('/')}/app/updates"; string listItems = string.Join( Environment.NewLine, - updates.Select(update => $"
  • {HtmlEncode(update.Title)}
    {HtmlEncode(update.Summary)}
  • ")); + updates.Select(update => $""" +
  • + {HtmlEncode(update.Title)}
    {HtmlEncode(update.Summary)}
    + {HtmlEncode(update.TitleFr)}
    {HtmlEncode(update.SummaryFr)} +
  • + """)); return $"""

    What's new in Socialize

    @@ -188,13 +105,6 @@ internal class ReleaseUpdateEmailService( """; } - private static string FormatBody(string? body) - { - return string.IsNullOrWhiteSpace(body) - ? string.Empty - : $"

    {HtmlEncode(body).Replace(Environment.NewLine, "
    ", StringComparison.Ordinal)}

    "; - } - private static string HtmlEncode(string? value) { return WebUtility.HtmlEncode(value ?? string.Empty); diff --git a/backend/src/Socialize.Api/Modules/ReleaseCommunications/Services/ReleaseUpdateRules.cs b/backend/src/Socialize.Api/Modules/ReleaseCommunications/Services/ReleaseUpdateRules.cs deleted file mode 100644 index 6ce69416..00000000 --- a/backend/src/Socialize.Api/Modules/ReleaseCommunications/Services/ReleaseUpdateRules.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Socialize.Api.Modules.ReleaseCommunications.Data; - -namespace Socialize.Api.Modules.ReleaseCommunications.Services; - -internal static class ReleaseUpdateRules -{ - public static bool TryParseCategory(string value, out ReleaseUpdateCategory category) - { - return TryParseEnum(value, out category); - } - - public static bool TryParseImportance(string value, out ReleaseUpdateImportance importance) - { - return TryParseEnum(value, out importance); - } - - public static bool TryParseAudience(string value, out ReleaseUpdateAudience audience) - { - return TryParseEnum(value, out audience); - } - - private static bool TryParseEnum(string value, out TEnum result) - where TEnum : struct - { - string normalized = value.Replace(" ", string.Empty, StringComparison.Ordinal); - return Enum.TryParse(normalized, ignoreCase: true, out result); - } -} diff --git a/backend/src/Socialize.Api/Modules/ReleaseCommunications/Services/ReleaseUpdateVisibility.cs b/backend/src/Socialize.Api/Modules/ReleaseCommunications/Services/ReleaseUpdateVisibility.cs index d66966af..39b0a742 100644 --- a/backend/src/Socialize.Api/Modules/ReleaseCommunications/Services/ReleaseUpdateVisibility.cs +++ b/backend/src/Socialize.Api/Modules/ReleaseCommunications/Services/ReleaseUpdateVisibility.cs @@ -1,46 +1,11 @@ -using System.Security.Claims; -using Microsoft.EntityFrameworkCore; -using Socialize.Api.Data; -using Socialize.Api.Modules.Identity.Contracts; -using Socialize.Api.Modules.Organizations.Data; -using Socialize.Api.Modules.Organizations.Services; using Socialize.Api.Modules.ReleaseCommunications.Data; namespace Socialize.Api.Modules.ReleaseCommunications.Services; internal static class ReleaseUpdateVisibility { - public static async Task GetAudienceContextAsync( - AppDbContext dbContext, - ClaimsPrincipal user, - Guid userId, - CancellationToken ct) + public static IQueryable VisibleToUsers(this IQueryable query) { - bool isDeveloper = user.IsInRole(KnownRoles.Developer); - bool isOrganizationOwner = await dbContext.Organizations.AnyAsync( - organization => organization.OwnerUserId == userId, - ct) - || await dbContext.OrganizationMemberships.AnyAsync( - membership => - membership.UserId == userId && - membership.Role == OrganizationRoles.Owner, - ct); - - return new ReleaseUpdateAudienceContext(isDeveloper, isOrganizationOwner); - } - - public static IQueryable VisibleTo( - this IQueryable query, - ReleaseUpdateAudienceContext context) - { - return query.Where(update => - update.Status == ReleaseUpdateStatus.Published && - (update.Audience == ReleaseUpdateAudience.Everyone || - (update.Audience == ReleaseUpdateAudience.OrganizationOwners && context.IsOrganizationOwner) || - (update.Audience == ReleaseUpdateAudience.Developers && context.IsDeveloper))); + return query.Where(update => update.Status == ReleaseUpdateStatus.Published); } } - -internal record ReleaseUpdateAudienceContext( - bool IsDeveloper, - bool IsOrganizationOwner); diff --git a/backend/tests/Socialize.Tests/ReleaseCommunications/ReleaseUpdateRulesTests.cs b/backend/tests/Socialize.Tests/ReleaseCommunications/ReleaseUpdateRulesTests.cs index 66e1a008..ae631fcb 100644 --- a/backend/tests/Socialize.Tests/ReleaseCommunications/ReleaseUpdateRulesTests.cs +++ b/backend/tests/Socialize.Tests/ReleaseCommunications/ReleaseUpdateRulesTests.cs @@ -6,64 +6,16 @@ namespace Socialize.Tests.ReleaseCommunications; public class ReleaseUpdateRulesTests { - [Theory] - [InlineData("Feature", ReleaseUpdateCategory.Feature)] - [InlineData("improvement", ReleaseUpdateCategory.Improvement)] - [InlineData("Breaking Change", ReleaseUpdateCategory.BreakingChange)] - [InlineData("BreakingChange", ReleaseUpdateCategory.BreakingChange)] - internal void TryParseCategory_accepts_supported_categories(string value, ReleaseUpdateCategory expected) - { - bool parsed = ReleaseUpdateRules.TryParseCategory(value, out ReleaseUpdateCategory category); - - Assert.True(parsed); - Assert.Equal(expected, category); - } - - [Theory] - [InlineData("")] - [InlineData("Security")] - [InlineData("Maintenance")] - public void TryParseCategory_rejects_unsupported_categories(string value) - { - bool parsed = ReleaseUpdateRules.TryParseCategory(value, out _); - - Assert.False(parsed); - } - - [Theory] - [InlineData("Normal", ReleaseUpdateImportance.Normal)] - [InlineData("important", ReleaseUpdateImportance.Important)] - internal void TryParseImportance_accepts_supported_importance(string value, ReleaseUpdateImportance expected) - { - bool parsed = ReleaseUpdateRules.TryParseImportance(value, out ReleaseUpdateImportance importance); - - Assert.True(parsed); - Assert.Equal(expected, importance); - } - - [Theory] - [InlineData("Everyone", ReleaseUpdateAudience.Everyone)] - [InlineData("Organization Owners", ReleaseUpdateAudience.OrganizationOwners)] - [InlineData("developers", ReleaseUpdateAudience.Developers)] - internal void TryParseAudience_accepts_supported_audiences(string value, ReleaseUpdateAudience expected) - { - bool parsed = ReleaseUpdateRules.TryParseAudience(value, out ReleaseUpdateAudience audience); - - Assert.True(parsed); - Assert.Equal(expected, audience); - } - [Fact] - public void ToDto_formats_breaking_change_category_for_display() + public void ToDto_maps_summary_to_description() { ReleaseUpdate update = new() { Id = Guid.NewGuid(), Title = "API change", Summary = "A workflow API changed.", - Category = ReleaseUpdateCategory.BreakingChange, - Importance = ReleaseUpdateImportance.Important, - Audience = ReleaseUpdateAudience.Developers, + TitleFr = "Changement API", + SummaryFr = "Une API du flux de travail a change.", Status = ReleaseUpdateStatus.Published, CreatedAt = DateTimeOffset.UtcNow, UpdatedAt = DateTimeOffset.UtcNow, @@ -73,18 +25,22 @@ public class ReleaseUpdateRulesTests ReleaseUpdateDto dto = update.ToDto(isRead: true); - Assert.Equal("Breaking Change", dto.Category); + Assert.Equal("A workflow API changed.", dto.Description); + Assert.Equal("API change", dto.TitleEn); + Assert.Equal("A workflow API changed.", dto.DescriptionEn); + Assert.Equal("Changement API", dto.TitleFr); + Assert.Equal("Une API du flux de travail a change.", dto.DescriptionFr); Assert.True(dto.IsRead); } [Fact] - public void VisibleTo_returns_everyone_updates_for_any_authenticated_user() + public void VisibleToUsers_returns_published_updates() { - ReleaseUpdate update = NewPublishedUpdate(ReleaseUpdateAudience.Everyone); + ReleaseUpdate update = NewPublishedUpdate(); List visibleUpdates = new[] { update } .AsQueryable() - .VisibleTo(new ReleaseUpdateAudienceContext(IsDeveloper: false, IsOrganizationOwner: false)) + .VisibleToUsers() .ToList(); Assert.Same(update, Assert.Single(visibleUpdates)); @@ -93,37 +49,17 @@ public class ReleaseUpdateRulesTests [Fact] public void VisibleTo_rejects_unpublished_updates() { - ReleaseUpdate update = NewPublishedUpdate(ReleaseUpdateAudience.Everyone); + ReleaseUpdate update = NewPublishedUpdate(); update.Status = ReleaseUpdateStatus.Draft; List visibleUpdates = new[] { update } .AsQueryable() - .VisibleTo(new ReleaseUpdateAudienceContext(IsDeveloper: true, IsOrganizationOwner: true)) + .VisibleToUsers() .ToList(); Assert.Empty(visibleUpdates); } - [Fact] - public void VisibleTo_requires_matching_restricted_audience() - { - ReleaseUpdate ownerUpdate = NewPublishedUpdate(ReleaseUpdateAudience.OrganizationOwners); - ReleaseUpdate developerUpdate = NewPublishedUpdate(ReleaseUpdateAudience.Developers); - - List ownerVisibleUpdates = new[] { ownerUpdate, developerUpdate } - .AsQueryable() - .VisibleTo(new ReleaseUpdateAudienceContext(IsDeveloper: false, IsOrganizationOwner: true)) - .ToList(); - - List developerVisibleUpdates = new[] { ownerUpdate, developerUpdate } - .AsQueryable() - .VisibleTo(new ReleaseUpdateAudienceContext(IsDeveloper: true, IsOrganizationOwner: false)) - .ToList(); - - Assert.Same(ownerUpdate, Assert.Single(ownerVisibleUpdates)); - Assert.Same(developerUpdate, Assert.Single(developerVisibleUpdates)); - } - [Fact] public void CreateMissingReadReceipts_creates_receipts_only_for_unread_visible_updates() { @@ -172,16 +108,15 @@ public class ReleaseUpdateRulesTests Assert.False(ReleaseUpdateEmailRules.CanSendDigest(lastSentBefore.AddMinutes(1), lastSentBefore)); } - private static ReleaseUpdate NewPublishedUpdate(ReleaseUpdateAudience audience) + private static ReleaseUpdate NewPublishedUpdate() { return new ReleaseUpdate { Id = Guid.NewGuid(), Title = "Update", Summary = "Something changed.", - Category = ReleaseUpdateCategory.Improvement, - Importance = ReleaseUpdateImportance.Normal, - Audience = audience, + TitleFr = "Mise a jour", + SummaryFr = "Quelque chose a change.", Status = ReleaseUpdateStatus.Published, CreatedAt = DateTimeOffset.UtcNow, UpdatedAt = DateTimeOffset.UtcNow, diff --git a/docs/FEATURES/release-communications.md b/docs/FEATURES/release-communications.md index cc58a942..ba2668c2 100644 --- a/docs/FEATURES/release-communications.md +++ b/docs/FEATURES/release-communications.md @@ -112,7 +112,7 @@ The system should not rely only on the user's last login timestamp. Login is one - Developer-only release communication back office: - `/app/developer/updates` - `/app/developer/updates/:id` - - `/app/developer/release-commits` + - `/app/developer/release-notes` Feature-owned frontend code belongs under: @@ -235,9 +235,8 @@ GET /api/developer/release-updates/{id} PUT /api/developer/release-updates/{id} POST /api/developer/release-updates/{id}/publish POST /api/developer/release-updates/{id}/archive -POST /api/developer/release-updates/{id}/send-email GET /api/developer/release-commits -POST /api/developer/release-commits/import +POST /api/developer/release-commits/refresh POST /api/developer/release-commits/{sha}/link POST /api/developer/release-commits/{sha}/unlink POST /api/developer/release-commits/{sha}/internal-only diff --git a/docs/TASKS/release-communications/003-developer-commit-reconciliation.md b/docs/TASKS/release-communications/003-developer-commit-reconciliation.md index 9487cd66..eaf265ce 100644 --- a/docs/TASKS/release-communications/003-developer-commit-reconciliation.md +++ b/docs/TASKS/release-communications/003-developer-commit-reconciliation.md @@ -24,7 +24,7 @@ Add the developer back-office workflow for importing shipped commits and matchin - mark a commit internal-only - mark a commit ignored - Add developer-only frontend screens: - - `/app/developer/release-commits` + - `/app/developer/release-notes` - linked commits on `/app/developer/updates/:id` - Add repository-backed import from configured HTTPS repository settings. - Add a selected-commit workflow to copy commit SHA/details and create a draft update entry linked to those commits. diff --git a/frontend/src/api/schema.d.ts b/frontend/src/api/schema.d.ts index e3473144..4143e7b2 100644 --- a/frontend/src/api/schema.d.ts +++ b/frontend/src/api/schema.d.ts @@ -164,22 +164,6 @@ export interface paths { patch?: never; trace?: never; }; - "/api/developer/release-commits/import": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - get?: never; - put?: never; - post: operations["SocializeApiModulesReleaseCommunicationsHandlersImportDeveloperReleaseCommitsHandler"]; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; "/api/developer/release-commits": { parameters: { query?: never; @@ -260,7 +244,7 @@ export interface paths { patch?: never; trace?: never; }; - "/api/developer/release-updates/{id}/send-email": { + "/api/developer/release-commits/refresh": { parameters: { query?: never; header?: never; @@ -269,7 +253,7 @@ export interface paths { }; get?: never; put?: never; - post: operations["SocializeApiModulesReleaseCommunicationsHandlersSendDeveloperReleaseUpdateEmailHandler"]; + post: operations["SocializeApiModulesReleaseCommunicationsHandlersRefreshDeveloperReleaseCommitsHandler"]; delete?: never; options?: never; head?: never; @@ -292,6 +276,22 @@ export interface paths { patch?: never; trace?: never; }; + "/api/developer/release-commits/{sha}/link-first-release": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["SocializeApiModulesReleaseCommunicationsHandlersLinkFirstReleaseCommitsHandler"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/api/developer/release-commits/{sha}/unlink": { parameters: { query?: never; @@ -1471,15 +1471,12 @@ export interface components { /** Format: guid */ id?: string; title?: string; - summary?: string; - body?: string | null; - category?: string; - importance?: string; - audience?: string; + description?: string; + titleEn?: string; + descriptionEn?: string; + titleFr?: string; + descriptionFr?: string; status?: string; - deploymentLabel?: string | null; - buildVersion?: string | null; - commitRange?: string | null; /** Format: date-time */ createdAt?: string; /** Format: date-time */ @@ -1488,25 +1485,13 @@ export interface components { publishedAt?: string | null; /** Format: date-time */ archivedAt?: string | null; - /** Format: guid */ - manualEmailSentByUserId?: string | null; - /** Format: date-time */ - manualEmailSentAt?: string | null; - manualEmailAudience?: string | null; - /** Format: int32 */ - manualEmailRecipientCount?: number | null; isRead?: boolean; }; SocializeApiModulesReleaseCommunicationsHandlersCreateDeveloperReleaseUpdateRequest: { - title: string; - summary: string; - body?: string | null; - category: string; - importance: string; - audience: string; - deploymentLabel?: string | null; - buildVersion?: string | null; - commitRange?: string | null; + titleEn: string; + descriptionEn: string; + titleFr: string; + descriptionFr: string; }; SocializeApiModulesReleaseCommunicationsContractsReleaseUpdateUnreadSummaryDto: { /** Format: int32 */ @@ -1515,15 +1500,6 @@ export interface components { importantUnreadCount?: number; updates?: components["schemas"]["SocializeApiModulesReleaseCommunicationsContractsReleaseUpdateDto"][]; }; - SocializeApiModulesReleaseCommunicationsContractsReleaseCommitImportResultDto: { - /** Format: int32 */ - importedCount?: number; - /** Format: int32 */ - updatedCount?: number; - /** Format: int32 */ - skippedCount?: number; - commits?: components["schemas"]["SocializeApiModulesReleaseCommunicationsContractsReleaseCommitDto"][]; - }; SocializeApiModulesReleaseCommunicationsContractsReleaseCommitDto: { sha?: string; shortSha?: string; @@ -1545,52 +1521,32 @@ export interface components { /** Format: date-time */ updatedAt?: string; }; - SocializeApiModulesReleaseCommunicationsHandlersImportDeveloperReleaseCommitsRequest: { - sinceSha?: string | null; - untilSha?: string | null; - sourceBranch?: string | null; - deploymentLabel?: string | null; - commits?: components["schemas"]["SocializeApiModulesReleaseCommunicationsHandlersImportDeveloperReleaseCommitDto"][] | null; - }; - SocializeApiModulesReleaseCommunicationsHandlersImportDeveloperReleaseCommitDto: { - sha?: string; - shortSha?: string | null; - subject?: string; - authorName?: string | null; - authorEmail?: string | null; - /** Format: date-time */ - authoredAt?: string | null; - /** Format: date-time */ - committedAt?: string | null; - sourceBranch?: string | null; - deploymentLabel?: string | null; - externalUrl?: string | null; - }; - SocializeApiModulesReleaseCommunicationsContractsReleaseUpdateEmailSendResultDto: { + SocializeApiModulesReleaseCommunicationsContractsReleaseCommitRefreshResultDto: { /** Format: int32 */ - recipientCount?: number; - /** Format: date-time */ - sentAt?: string; - testMode?: boolean; - }; - SocializeApiModulesReleaseCommunicationsHandlersSendDeveloperReleaseUpdateEmailRequest: { - testMode?: boolean; - confirmResend?: boolean; + createdCount?: number; + /** Format: int32 */ + updatedCount?: number; + /** Format: int32 */ + skippedCount?: number; + commits?: components["schemas"]["SocializeApiModulesReleaseCommunicationsContractsReleaseCommitDto"][]; }; SocializeApiModulesReleaseCommunicationsHandlersLinkDeveloperReleaseCommitRequest: { /** Format: guid */ releaseUpdateId?: string; }; + SocializeApiModulesReleaseCommunicationsContractsReleaseCommitBulkLinkResultDto: { + /** Format: int32 */ + linkedCount?: number; + }; + SocializeApiModulesReleaseCommunicationsHandlersLinkFirstReleaseCommitsRequest: { + /** Format: guid */ + releaseUpdateId?: string; + }; SocializeApiModulesReleaseCommunicationsHandlersUpdateDeveloperReleaseUpdateRequest: { - title: string; - summary: string; - body?: string | null; - category: string; - importance: string; - audience: string; - deploymentLabel?: string | null; - buildVersion?: string | null; - commitRange?: string | null; + titleEn: string; + descriptionEn: string; + titleFr: string; + descriptionFr: string; }; SocializeApiModulesOrganizationsHandlersOrganizationMemberDto: { /** Format: guid */ @@ -2871,44 +2827,6 @@ export interface operations { }; }; }; - SocializeApiModulesReleaseCommunicationsHandlersImportDeveloperReleaseCommitsHandler: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["SocializeApiModulesReleaseCommunicationsHandlersImportDeveloperReleaseCommitsRequest"]; - }; - }; - responses: { - /** @description Success */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["SocializeApiModulesReleaseCommunicationsContractsReleaseCommitImportResultDto"]; - }; - }; - /** @description Unauthorized */ - 401: { - headers: { - [name: string]: unknown; - }; - content?: never; - }; - /** @description Forbidden */ - 403: { - headers: { - [name: string]: unknown; - }; - content?: never; - }; - }; - }; SocializeApiModulesReleaseCommunicationsHandlersListDeveloperReleaseCommitsHandler: { parameters: { query?: never; @@ -3058,20 +2976,14 @@ export interface operations { }; }; }; - SocializeApiModulesReleaseCommunicationsHandlersSendDeveloperReleaseUpdateEmailHandler: { + SocializeApiModulesReleaseCommunicationsHandlersRefreshDeveloperReleaseCommitsHandler: { parameters: { query?: never; header?: never; - path: { - id: string; - }; + path?: never; cookie?: never; }; - requestBody: { - content: { - "application/json": components["schemas"]["SocializeApiModulesReleaseCommunicationsHandlersSendDeveloperReleaseUpdateEmailRequest"]; - }; - }; + requestBody?: never; responses: { /** @description Success */ 200: { @@ -3079,7 +2991,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["SocializeApiModulesReleaseCommunicationsContractsReleaseUpdateEmailSendResultDto"]; + "application/json": components["schemas"]["SocializeApiModulesReleaseCommunicationsContractsReleaseCommitRefreshResultDto"]; }; }; /** @description Unauthorized */ @@ -3138,6 +3050,46 @@ export interface operations { }; }; }; + SocializeApiModulesReleaseCommunicationsHandlersLinkFirstReleaseCommitsHandler: { + parameters: { + query?: never; + header?: never; + path: { + sha: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["SocializeApiModulesReleaseCommunicationsHandlersLinkFirstReleaseCommitsRequest"]; + }; + }; + responses: { + /** @description Success */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SocializeApiModulesReleaseCommunicationsContractsReleaseCommitBulkLinkResultDto"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Forbidden */ + 403: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; SocializeApiModulesReleaseCommunicationsHandlersUnlinkDeveloperReleaseCommitHandler: { parameters: { query?: never; diff --git a/frontend/src/assets/main.css b/frontend/src/assets/main.css index 7e7cebdd..b0eaa450 100644 --- a/frontend/src/assets/main.css +++ b/frontend/src/assets/main.css @@ -1,4 +1,5 @@ -@import "tailwindcss"; +@import "tailwindcss/theme.css"; +@import "tailwindcss/utilities.css"; @custom-variant dark (&:where(.dark, .dark *)); @theme inline { @@ -29,59 +30,6 @@ textarea::placeholder { opacity: 1; } -.v-application { - background: rgb(var(--v-theme-background)) !important; - color: rgb(var(--v-theme-on-background)); -} - -.v-card, -.v-sheet, -.v-list, -.v-menu > .v-overlay__content, -.v-dialog > .v-overlay__content { - background-color: rgb(var(--v-theme-surface)) !important; - border: 1px solid rgb(var(--v-theme-border)); -} - -.v-field { - background-color: rgb(var(--v-theme-control)) !important; - color: rgb(var(--v-theme-on-surface)); -} - -.v-field:hover { - background-color: rgb(var(--v-theme-control-hover)) !important; -} - -.v-field--focused { - background-color: rgb(var(--v-theme-control-focus)) !important; -} - -.v-field__outline { - color: rgb(var(--v-theme-border-strong)); -} - -.v-field--focused .v-field__outline { - color: rgb(var(--v-theme-highlight)); -} - -.v-field__input, -.v-field-label { - color: rgb(var(--v-theme-on-surface)); -} - -.v-select .v-field .v-field__input > input, -.v-select .v-field .v-field__input > input::placeholder { - color: transparent !important; - caret-color: transparent; -} - -.panel, -[class$='-panel'], -[class$='-card'], -div.card { - border-color: rgb(var(--v-theme-border)) !important; -} - @layer components { .btn { @apply min-w-24 w-full; diff --git a/frontend/src/features/release-communications/formatReleaseDescription.js b/frontend/src/features/release-communications/formatReleaseDescription.js new file mode 100644 index 00000000..91467a1b --- /dev/null +++ b/frontend/src/features/release-communications/formatReleaseDescription.js @@ -0,0 +1,54 @@ +export function formatReleaseDescription(value) { + const lines = String(value ?? '').replace(/\r\n?/g, '\n').split('\n'); + const blocks = []; + let paragraphLines = []; + let listItems = []; + + function flushParagraph() { + if (!paragraphLines.length) { + return; + } + + blocks.push({ + type: 'paragraph', + text: paragraphLines.join(' ').trim(), + }); + paragraphLines = []; + } + + function flushList() { + if (!listItems.length) { + return; + } + + blocks.push({ + type: 'list', + items: listItems, + }); + listItems = []; + } + + lines.forEach(line => { + const trimmed = line.trim(); + if (!trimmed) { + flushParagraph(); + flushList(); + return; + } + + const bullet = trimmed.match(/^[-*]\s+(.+)$/); + if (bullet) { + flushParagraph(); + listItems.push(bullet[1].trim()); + return; + } + + flushList(); + paragraphLines.push(trimmed); + }); + + flushParagraph(); + flushList(); + + return blocks; +} diff --git a/frontend/src/features/release-communications/stores/releaseCommunicationsStore.js b/frontend/src/features/release-communications/stores/releaseCommunicationsStore.js index dfad2ddd..bc721fd5 100644 --- a/frontend/src/features/release-communications/stores/releaseCommunicationsStore.js +++ b/frontend/src/features/release-communications/stores/releaseCommunicationsStore.js @@ -3,17 +3,9 @@ import { defineStore } from 'pinia'; import { useClient } from '@/plugins/api.js'; const DEFAULT_COMMIT_FILTERS = Object.freeze({ - status: '', - updateId: '', - author: '', - search: '', + inclusion: 'notIncluded', }); -export const RELEASE_UPDATE_CATEGORIES = ['Feature', 'Improvement', 'Fix', 'Breaking Change']; -export const RELEASE_UPDATE_IMPORTANCE = ['Normal', 'Important']; -export const RELEASE_UPDATE_AUDIENCES = ['Everyone', 'OrganizationOwners', 'Developers']; -export const RELEASE_COMMIT_STATUSES = ['Unreviewed', 'Linked', 'InternalOnly', 'Ignored']; - export const useReleaseCommunicationsStore = defineStore('release-communications', () => { const client = useClient(); const updates = ref([]); @@ -21,11 +13,11 @@ export const useReleaseCommunicationsStore = defineStore('release-communications const developerUpdates = ref([]); const selectedUpdate = ref(null); const commits = ref([]); + const selectedCommitShas = ref([]); const commitFilters = ref({ ...DEFAULT_COMMIT_FILTERS }); const isLoading = ref(false); const isSaving = ref(false); - const isSendingEmail = ref(false); - const isImporting = ref(false); + const isRefreshingCommits = ref(false); const error = ref(null); const unreadCount = computed(() => unreadSummary.value?.unreadCount ?? 0); @@ -35,40 +27,15 @@ export const useReleaseCommunicationsStore = defineStore('release-communications ); const filteredCommits = computed(() => { - const query = commitFilters.value.search.trim().toLowerCase(); - const author = commitFilters.value.author.trim().toLowerCase(); - return commits.value.filter(commit => { - if (commitFilters.value.status && commit.communicationStatus !== commitFilters.value.status) { + if (commitFilters.value.inclusion === 'included' && !commit.releaseUpdateId) { return false; } - if (commitFilters.value.updateId && commit.releaseUpdateId !== commitFilters.value.updateId) { + if (commitFilters.value.inclusion === 'notIncluded' && commit.releaseUpdateId) { return false; } - if (author) { - const authorText = `${commit.authorName ?? ''} ${commit.authorEmail ?? ''}`.toLowerCase(); - if (!authorText.includes(author)) { - return false; - } - } - - if (query) { - const haystack = [ - commit.sha, - commit.shortSha, - commit.subject, - commit.authorName, - commit.authorEmail, - commit.deploymentLabel, - commit.sourceBranch, - ].filter(Boolean).join(' ').toLowerCase(); - if (!haystack.includes(query)) { - return false; - } - } - return true; }); }); @@ -159,28 +126,19 @@ export const useReleaseCommunicationsStore = defineStore('release-communications return response.data; } - async function sendUpdateEmail(id, payload) { - isSendingEmail.value = true; - try { - return (await client.post(`/api/developer/release-updates/${id}/send-email`, payload)).data; - } finally { - isSendingEmail.value = false; - } - } - async function loadCommits() { const response = await client.get('/api/developer/release-commits'); commits.value = response.data ?? []; } - async function importCommits(payload) { - isImporting.value = true; + async function refreshCommits() { + isRefreshingCommits.value = true; try { - const response = await client.post('/api/developer/release-commits/import', payload); + const response = await client.post('/api/developer/release-commits/refresh'); await loadCommits(); return response.data; } finally { - isImporting.value = false; + isRefreshingCommits.value = false; } } @@ -194,6 +152,12 @@ export const useReleaseCommunicationsStore = defineStore('release-communications await Promise.all([loadCommits(), loadDeveloperUpdates()]); } + async function linkFirstReleaseCommits(anchorSha, releaseUpdateId) { + const response = await client.post(`/api/developer/release-commits/${anchorSha}/link-first-release`, { releaseUpdateId }); + await Promise.all([loadCommits(), loadDeveloperUpdates()]); + return response.data; + } + async function unlinkCommit(sha) { await client.post(`/api/developer/release-commits/${sha}/unlink`); await loadCommits(); @@ -213,12 +177,23 @@ export const useReleaseCommunicationsStore = defineStore('release-communications commitFilters.value = { ...DEFAULT_COMMIT_FILTERS }; } + function setCommitSelected(sha, selected) { + selectedCommitShas.value = selected + ? [...new Set([...selectedCommitShas.value, sha])] + : selectedCommitShas.value.filter(selectedSha => selectedSha !== sha); + } + + function clearSelectedCommits() { + selectedCommitShas.value = []; + } + return { updates, unreadSummary, developerUpdates, selectedUpdate, commits, + selectedCommitShas, commitFilters, filteredCommits, unreadCount, @@ -226,8 +201,7 @@ export const useReleaseCommunicationsStore = defineStore('release-communications unreviewedCommitCount, isLoading, isSaving, - isSendingEmail, - isImporting, + isRefreshingCommits, error, loadUserUpdates, loadUnreadSummary, @@ -238,14 +212,16 @@ export const useReleaseCommunicationsStore = defineStore('release-communications saveDeveloperUpdate, publishUpdate, archiveUpdate, - sendUpdateEmail, loadCommits, - importCommits, + refreshCommits, linkCommit, linkCommitsToUpdate, + linkFirstReleaseCommits, unlinkCommit, markCommitInternalOnly, ignoreCommit, resetCommitFilters, + setCommitSelected, + clearSelectedCommits, }; }); diff --git a/frontend/src/features/release-communications/views/DeveloperReleaseCommitsView.vue b/frontend/src/features/release-communications/views/DeveloperReleaseCommitsView.vue index 2c1091c7..7df3e3e4 100644 --- a/frontend/src/features/release-communications/views/DeveloperReleaseCommitsView.vue +++ b/frontend/src/features/release-communications/views/DeveloperReleaseCommitsView.vue @@ -1,114 +1,239 @@ @@ -151,131 +314,333 @@ {{ store.unreviewedCommitCount }} {{ t('releaseCommunications.commits.unreviewed') }} -
    -
    - - - - - - {{ t('releaseCommunications.commits.fetch') }} -
    - - {{ t('releaseCommunications.commits.import') }} - - {{ t('releaseCommunications.commits.importResult', { imported: importResult.importedCount, updated: importResult.updatedCount, skipped: importResult.skippedCount }) }} - -
    - -
    -
    - {{ t('releaseCommunications.commits.selected', { count: selectedCommits.length }) }} -
    - {{ t('releaseCommunications.commits.copySelected') }} - {{ t('releaseCommunications.commits.clearSelection') }} -
    -
    - {{ copyResult }} -
    - - - -
    - - - -
    - {{ t('releaseCommunications.commits.createUpdate') }} - -
    - -
    - - - - - {{ t('releaseCommunications.commits.clear') }} -
    - -
    -
    +
    + + + + +
    + + + +
    + + +
    +
    +

    {{ t('releaseCommunications.developer.creationTitle') }}

    +
    + + {{ t('releaseCommunications.developer.publish') }} + + + {{ selectedCommits.length && !editingId ? t('create') : t('save') }} + + + {{ t('cancel') }} + +
    +
    + +
    +
    + + {{ t('releaseCommunications.english') }} + {{ t('releaseCommunications.french') }} + + + + +
    + + +
    +
    + +
    + + +
    +
    +
    +
    + +
    +

    {{ t('releaseCommunications.developer.linkedCommits') }}

    +
    {{ t('releaseCommunications.developer.noLinkedCommits') }}
    +
    + {{ commit.shortSha }} + {{ commit.subject }} +
    +
    + +
    +

    {{ t('releaseCommunications.commits.selectedCommits') }}

    +
      +
    • + {{ commit.subject }} +
    • +
    +
    + +
    +
    +
    diff --git a/frontend/src/features/release-communications/views/DeveloperUpdatesView.vue b/frontend/src/features/release-communications/views/DeveloperUpdatesView.vue deleted file mode 100644 index 90331f72..00000000 --- a/frontend/src/features/release-communications/views/DeveloperUpdatesView.vue +++ /dev/null @@ -1,246 +0,0 @@ - - - - - diff --git a/frontend/src/features/release-communications/views/UpdatesView.vue b/frontend/src/features/release-communications/views/UpdatesView.vue index e0e34952..7321e6f0 100644 --- a/frontend/src/features/release-communications/views/UpdatesView.vue +++ b/frontend/src/features/release-communications/views/UpdatesView.vue @@ -2,9 +2,10 @@ import { computed, onMounted } from 'vue'; import { useI18n } from 'vue-i18n'; import { useRoute } from 'vue-router'; + import { formatReleaseDescription } from '@/features/release-communications/formatReleaseDescription.js'; import { useReleaseCommunicationsStore } from '@/features/release-communications/stores/releaseCommunicationsStore.js'; - const { t } = useI18n(); + const { locale, t } = useI18n(); const route = useRoute(); const store = useReleaseCommunicationsStore(); @@ -12,31 +13,46 @@ onMounted(async () => { await store.loadUserUpdates(); - if (highlightedId.value) { - await store.markRead(highlightedId.value); + if (store.updates.some(update => !update.isRead)) { + await store.markAllRead(); } }); function formatDate(value) { - return value ? new Date(value).toLocaleString() : ''; + if (!value) { + return ''; + } + + return new Intl.DateTimeFormat(locale.value, { + dateStyle: 'long', + }).format(new Date(value)); + } + + function updateTitle(update) { + return locale.value.startsWith('fr') + ? update.titleFr || update.title + : update.titleEn || update.title; + } + + function updateDescription(update) { + return locale.value.startsWith('fr') + ? update.descriptionFr || update.description + : update.descriptionEn || update.description; + } + + function updateDescriptionBlocks(update) { + return formatReleaseDescription(updateDescription(update)); }