diff --git a/backend/src/Socialize.Api/Data/AppDbContext.cs b/backend/src/Socialize.Api/Data/AppDbContext.cs index 7ff2235..b50eac2 100644 --- a/backend/src/Socialize.Api/Data/AppDbContext.cs +++ b/backend/src/Socialize.Api/Data/AppDbContext.cs @@ -8,7 +8,7 @@ using Socialize.Api.Modules.ContentItems.Data; using Socialize.Api.Modules.Feedback.Data; using Socialize.Api.Modules.Identity.Data; using Socialize.Api.Modules.Notifications.Data; -using Socialize.Api.Modules.Projects.Data; +using Socialize.Api.Modules.Campaigns.Data; using Socialize.Api.Modules.Workspaces.Data; namespace Socialize.Api.Data; @@ -20,7 +20,7 @@ public class AppDbContext( public DbSet Workspaces => Set(); public DbSet WorkspaceInvites => Set(); public DbSet Clients => Set(); - public DbSet Projects => Set(); + public DbSet Campaigns => Set(); public DbSet ContentItems => Set(); public DbSet ContentItemRevisions => Set(); public DbSet Assets => Set(); @@ -43,7 +43,7 @@ public class AppDbContext( builder.ConfigureWorkspacesModule(); builder.ConfigureClientsModule(); - builder.ConfigureProjectsModule(); + builder.ConfigureCampaignsModule(); builder.ConfigureContentItemsModule(); builder.ConfigureAssetsModule(); builder.ConfigureCommentsModule(); diff --git a/backend/src/Socialize.Api/Infrastructure/Development/DevelopmentSeedExtensions.cs b/backend/src/Socialize.Api/Infrastructure/Development/DevelopmentSeedExtensions.cs index ff37c0e..e503efb 100644 --- a/backend/src/Socialize.Api/Infrastructure/Development/DevelopmentSeedExtensions.cs +++ b/backend/src/Socialize.Api/Infrastructure/Development/DevelopmentSeedExtensions.cs @@ -10,7 +10,7 @@ using Socialize.Api.Modules.Comments.Data; using Socialize.Api.Modules.ContentItems.Data; using Socialize.Api.Modules.Clients.Data; using Socialize.Api.Modules.Notifications.Data; -using Socialize.Api.Modules.Projects.Data; +using Socialize.Api.Modules.Campaigns.Data; using Socialize.Api.Modules.Workspaces.Data; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Options; @@ -22,8 +22,8 @@ public static class DevelopmentSeedExtensions private static readonly Guid WorkspaceId = Guid.Parse("11111111-1111-1111-1111-111111111111"); private static readonly Guid ScopedClientId = Guid.Parse("22222222-2222-2222-2222-222222222222"); private static readonly Guid HiddenClientId = Guid.Parse("22222222-2222-2222-2222-333333333333"); - private static readonly Guid ScopedProjectId = Guid.Parse("33333333-3333-3333-3333-333333333333"); - private static readonly Guid HiddenProjectId = Guid.Parse("33333333-3333-3333-3333-444444444444"); + private static readonly Guid ScopedCampaignId = Guid.Parse("33333333-3333-3333-3333-333333333333"); + private static readonly Guid HiddenCampaignId = Guid.Parse("33333333-3333-3333-3333-444444444444"); private static readonly Guid ScopedContentItemId = Guid.Parse("44444444-4444-4444-4444-444444444444"); private static readonly Guid HiddenContentItemId = Guid.Parse("44444444-4444-4444-4444-555555555555"); private static readonly Guid ScopedAssetId = Guid.Parse("55555555-5555-5555-5555-555555555555"); @@ -99,7 +99,7 @@ public static class DevelopmentSeedExtensions [ new Claim(KnownClaims.WorkspaceScope, WorkspaceId.ToString()), new Claim(KnownClaims.ClientScope, ScopedClientId.ToString()), - new Claim(KnownClaims.ProjectScope, ScopedProjectId.ToString()), + new Claim(KnownClaims.CampaignScope, ScopedCampaignId.ToString()), ]); User dev = await EnsureUserAsync( @@ -200,7 +200,7 @@ public static class DevelopmentSeedExtensions IList existingClaims = await userManager.GetClaimsAsync(user); List managedClaims = existingClaims - .Where(claim => claim.Type is KnownClaims.WorkspaceScope or KnownClaims.ClientScope or KnownClaims.ProjectScope or KnownClaims.Persona) + .Where(claim => claim.Type is KnownClaims.WorkspaceScope or KnownClaims.ClientScope or KnownClaims.CampaignScope or KnownClaims.Persona) .ToList(); foreach (Claim claim in managedClaims) @@ -273,9 +273,9 @@ public static class DevelopmentSeedExtensions WorkspaceId, cancellationToken); - await UpsertProjectAsync( + await UpsertCampaignAsync( dbContext, - ScopedProjectId, + ScopedCampaignId, WorkspaceId, ScopedClientId, "Spring Launch", @@ -285,9 +285,9 @@ public static class DevelopmentSeedExtensions "Cross-channel launch campaign for the spring offer.", "Coordinate creative approvals before the final week.", cancellationToken); - await UpsertProjectAsync( + await UpsertCampaignAsync( dbContext, - HiddenProjectId, + HiddenCampaignId, WorkspaceId, HiddenClientId, "Summer Retention", @@ -303,7 +303,7 @@ public static class DevelopmentSeedExtensions ScopedContentItemId, WorkspaceId, ScopedClientId, - ScopedProjectId, + ScopedCampaignId, "Spring launch hero video", "Fresh seasonal menu launch across Instagram and TikTok.", "Instagram Reel, TikTok", @@ -317,7 +317,7 @@ public static class DevelopmentSeedExtensions HiddenContentItemId, WorkspaceId, HiddenClientId, - HiddenProjectId, + HiddenCampaignId, "Bakery loyalty carousel", "Reward regular customers with a four-card retention carousel.", "Instagram Carousel", @@ -491,7 +491,7 @@ public static class DevelopmentSeedExtensions await dbContext.SaveChangesAsync(cancellationToken); } - private static async Task UpsertProjectAsync( + private static async Task UpsertCampaignAsync( AppDbContext dbContext, Guid id, Guid workspaceId, @@ -504,26 +504,26 @@ public static class DevelopmentSeedExtensions string? notes, CancellationToken cancellationToken) { - Project? project = await dbContext.Projects.SingleOrDefaultAsync(candidate => candidate.Id == id, cancellationToken); - if (project is null) + Campaign? campaign = await dbContext.Campaigns.SingleOrDefaultAsync(candidate => candidate.Id == id, cancellationToken); + if (campaign is null) { - project = new Project + campaign = new Campaign { Id = id, Name = string.Empty, Status = string.Empty, CreatedAt = DateTimeOffset.UtcNow, }; - dbContext.Projects.Add(project); + dbContext.Campaigns.Add(campaign); } - project.WorkspaceId = workspaceId; - project.ClientId = clientId; - project.Name = name; - project.Description = description; - project.Notes = notes; - project.Status = status; - project.StartDate = startDate; - project.EndDate = endDate; + campaign.WorkspaceId = workspaceId; + campaign.ClientId = clientId; + campaign.Name = name; + campaign.Description = description; + campaign.Notes = notes; + campaign.Status = status; + campaign.StartDate = startDate; + campaign.EndDate = endDate; await dbContext.SaveChangesAsync(cancellationToken); } @@ -532,7 +532,7 @@ public static class DevelopmentSeedExtensions Guid id, Guid workspaceId, Guid clientId, - Guid projectId, + Guid campaignId, string title, string publicationMessage, string publicationTargets, @@ -559,7 +559,7 @@ public static class DevelopmentSeedExtensions } item.WorkspaceId = workspaceId; item.ClientId = clientId; - item.ProjectId = projectId; + item.CampaignId = campaignId; item.Title = title; item.PublicationMessage = publicationMessage; item.PublicationTargets = publicationTargets; diff --git a/backend/src/Socialize.Api/Infrastructure/Security/AccessScopeService.cs b/backend/src/Socialize.Api/Infrastructure/Security/AccessScopeService.cs index 67e4f91..1fa5d87 100644 --- a/backend/src/Socialize.Api/Infrastructure/Security/AccessScopeService.cs +++ b/backend/src/Socialize.Api/Infrastructure/Security/AccessScopeService.cs @@ -36,21 +36,21 @@ public sealed class AccessScopeService || (CanAccessWorkspace(user, workspaceId) && user.GetClientScopeIds().Contains(clientId)); } - public bool CanAccessProject(ClaimsPrincipal user, Guid workspaceId, Guid clientId, Guid projectId) + public bool CanAccessCampaign(ClaimsPrincipal user, Guid workspaceId, Guid clientId, Guid campaignId) { return IsManager(user) - || (CanAccessClient(user, workspaceId, clientId) && user.GetProjectScopeIds().Contains(projectId)); + || (CanAccessClient(user, workspaceId, clientId) && user.GetCampaignScopeIds().Contains(campaignId)); } - public bool CanContributeToProject(ClaimsPrincipal user, Guid workspaceId, Guid clientId, Guid projectId) + public bool CanContributeToCampaign(ClaimsPrincipal user, Guid workspaceId, Guid clientId, Guid campaignId) { - return IsManager(user) || (IsProvider(user) && CanAccessProject(user, workspaceId, clientId, projectId)); + return IsManager(user) || (IsProvider(user) && CanAccessCampaign(user, workspaceId, clientId, campaignId)); } - public bool CanReviewContent(ClaimsPrincipal user, Guid workspaceId, Guid clientId, Guid projectId) + public bool CanReviewContent(ClaimsPrincipal user, Guid workspaceId, Guid clientId, Guid campaignId) { return IsManager(user) - || IsProvider(user) && CanAccessProject(user, workspaceId, clientId, projectId) + || IsProvider(user) && CanAccessCampaign(user, workspaceId, clientId, campaignId) || IsClient(user) && CanAccessClient(user, workspaceId, clientId); } } diff --git a/backend/src/Socialize.Api/Infrastructure/Security/ClaimsPrincipalExtensions.cs b/backend/src/Socialize.Api/Infrastructure/Security/ClaimsPrincipalExtensions.cs index a03ddde..ff07f53 100644 --- a/backend/src/Socialize.Api/Infrastructure/Security/ClaimsPrincipalExtensions.cs +++ b/backend/src/Socialize.Api/Infrastructure/Security/ClaimsPrincipalExtensions.cs @@ -23,9 +23,9 @@ public static class ClaimsPrincipalExtensions return claims.GetScopeIds(KnownClaims.ClientScope); } - public static IReadOnlyCollection GetProjectScopeIds(this ClaimsPrincipal claims) + public static IReadOnlyCollection GetCampaignScopeIds(this ClaimsPrincipal claims) { - return claims.GetScopeIds(KnownClaims.ProjectScope); + return claims.GetScopeIds(KnownClaims.CampaignScope); } public static string? GetPersona(this ClaimsPrincipal claims) diff --git a/backend/src/Socialize.Api/Infrastructure/Security/KnownClaims.cs b/backend/src/Socialize.Api/Infrastructure/Security/KnownClaims.cs index f84e27a..f48bef5 100644 --- a/backend/src/Socialize.Api/Infrastructure/Security/KnownClaims.cs +++ b/backend/src/Socialize.Api/Infrastructure/Security/KnownClaims.cs @@ -6,6 +6,6 @@ public static class KnownClaims public const string PortraitUrl = "portraitUrl"; public const string WorkspaceScope = "workspace"; public const string ClientScope = "client"; - public const string ProjectScope = "project"; + public const string CampaignScope = "campaign"; public const string Persona = "persona"; } diff --git a/backend/src/Socialize.Api/Migrations/20260501191447_RenameProjectsToCampaigns.Designer.cs b/backend/src/Socialize.Api/Migrations/20260501191447_RenameProjectsToCampaigns.Designer.cs new file mode 100644 index 0000000..f3a37d7 --- /dev/null +++ b/backend/src/Socialize.Api/Migrations/20260501191447_RenameProjectsToCampaigns.Designer.cs @@ -0,0 +1,1423 @@ +// +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("20260501191447_RenameProjectsToCampaigns")] + partial class RenameProjectsToCampaigns + { + /// + 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.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.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("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("IsResolved") + .HasColumnType("boolean"); + + b.Property("ParentCommentId") + .HasColumnType("uuid"); + + b.Property("ResolvedAt") + .HasColumnType("timestamp with time zone"); + + 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.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("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("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.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("OwnerUserId") + .HasColumnType("uuid"); + + b.Property("SchedulePostsAutomaticallyOnApproval") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("SendAutomaticApprovalReminders") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("TimeZone") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.HasKey("Id"); + + b.HasIndex("OwnerUserId"); + + b.HasIndex("Slug") + .IsUnique(); + + 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.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.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.Feedback.Data.FeedbackReport", b => + { + b.Navigation("ActivityEntries"); + + b.Navigation("Comments"); + + b.Navigation("Screenshot"); + + b.Navigation("Tags"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/backend/src/Socialize.Api/Migrations/20260501191447_RenameProjectsToCampaigns.cs b/backend/src/Socialize.Api/Migrations/20260501191447_RenameProjectsToCampaigns.cs new file mode 100644 index 0000000..b9e0912 --- /dev/null +++ b/backend/src/Socialize.Api/Migrations/20260501191447_RenameProjectsToCampaigns.cs @@ -0,0 +1,117 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Socialize.Api.Migrations +{ + /// + public partial class RenameProjectsToCampaigns : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.RenameTable( + name: "Projects", + newName: "Campaigns"); + + migrationBuilder.DropPrimaryKey( + name: "PK_Projects", + table: "Campaigns"); + + migrationBuilder.AddPrimaryKey( + name: "PK_Campaigns", + table: "Campaigns", + column: "Id"); + + migrationBuilder.RenameIndex( + name: "IX_Projects_WorkspaceId", + table: "Campaigns", + newName: "IX_Campaigns_WorkspaceId"); + + migrationBuilder.RenameIndex( + name: "IX_Projects_ClientId_Name", + table: "Campaigns", + newName: "IX_Campaigns_ClientId_Name"); + + migrationBuilder.RenameIndex( + name: "IX_Projects_ClientId", + table: "Campaigns", + newName: "IX_Campaigns_ClientId"); + + migrationBuilder.RenameColumn( + name: "ProjectName", + table: "FeedbackReports", + newName: "CampaignName"); + + migrationBuilder.RenameColumn( + name: "ProjectId", + table: "FeedbackReports", + newName: "CampaignId"); + + migrationBuilder.RenameColumn( + name: "ProjectId", + table: "ContentItems", + newName: "CampaignId"); + + migrationBuilder.RenameIndex( + name: "IX_ContentItems_ProjectId", + table: "ContentItems", + newName: "IX_ContentItems_CampaignId"); + + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropPrimaryKey( + name: "PK_Campaigns", + table: "Campaigns"); + + migrationBuilder.AddPrimaryKey( + name: "PK_Projects", + table: "Campaigns", + column: "Id"); + + migrationBuilder.RenameIndex( + name: "IX_Campaigns_WorkspaceId", + table: "Campaigns", + newName: "IX_Projects_WorkspaceId"); + + migrationBuilder.RenameIndex( + name: "IX_Campaigns_ClientId_Name", + table: "Campaigns", + newName: "IX_Projects_ClientId_Name"); + + migrationBuilder.RenameIndex( + name: "IX_Campaigns_ClientId", + table: "Campaigns", + newName: "IX_Projects_ClientId"); + + migrationBuilder.RenameTable( + name: "Campaigns", + newName: "Projects"); + + migrationBuilder.RenameColumn( + name: "CampaignName", + table: "FeedbackReports", + newName: "ProjectName"); + + migrationBuilder.RenameColumn( + name: "CampaignId", + table: "FeedbackReports", + newName: "ProjectId"); + + migrationBuilder.RenameColumn( + name: "CampaignId", + table: "ContentItems", + newName: "ProjectId"); + + migrationBuilder.RenameIndex( + name: "IX_ContentItems_CampaignId", + table: "ContentItems", + newName: "IX_ContentItems_ProjectId"); + + } + } +} diff --git a/backend/src/Socialize.Api/Migrations/AppDbContextModelSnapshot.cs b/backend/src/Socialize.Api/Migrations/AppDbContextModelSnapshot.cs index 72bbe9d..80004fd 100644 --- a/backend/src/Socialize.Api/Migrations/AppDbContextModelSnapshot.cs +++ b/backend/src/Socialize.Api/Migrations/AppDbContextModelSnapshot.cs @@ -438,6 +438,59 @@ namespace Socialize.Api.Migrations b.ToTable("AssetRevisions", (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.Clients.Data.Client", b => { b.Property("Id") @@ -549,6 +602,9 @@ namespace Socialize.Api.Migrations .ValueGeneratedOnAdd() .HasColumnType("uuid"); + b.Property("CampaignId") + .HasColumnType("uuid"); + b.Property("ClientId") .HasColumnType("uuid"); @@ -572,9 +628,6 @@ namespace Socialize.Api.Migrations .HasMaxLength(1024) .HasColumnType("character varying(1024)"); - b.Property("ProjectId") - .HasColumnType("uuid"); - b.Property("PublicationMessage") .IsRequired() .HasMaxLength(4000) @@ -600,9 +653,9 @@ namespace Socialize.Api.Migrations b.HasKey("Id"); - b.HasIndex("ClientId"); + b.HasIndex("CampaignId"); - b.HasIndex("ProjectId"); + b.HasIndex("ClientId"); b.HasIndex("WorkspaceId"); @@ -784,6 +837,13 @@ namespace Socialize.Api.Migrations .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)"); @@ -821,13 +881,6 @@ namespace Socialize.Api.Migrations b.Property("LastActivityAt") .HasColumnType("timestamp with time zone"); - b.Property("ProjectId") - .HasColumnType("uuid"); - - b.Property("ProjectName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - b.Property("ReporterDisplayName") .IsRequired() .HasMaxLength(256) @@ -1150,59 +1203,6 @@ namespace Socialize.Api.Migrations b.ToTable("NotificationEvents", (string)null); }); - modelBuilder.Entity("Socialize.Api.Modules.Projects.Data.Project", 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("Projects", (string)null); - }); - modelBuilder.Entity("Socialize.Api.Modules.Workspaces.Data.Workspace", b => { b.Property("Id") diff --git a/backend/src/Socialize.Api/Modules/Approvals/Handlers/GetApprovals.cs b/backend/src/Socialize.Api/Modules/Approvals/Handlers/GetApprovals.cs index 6f43907..e025331 100644 --- a/backend/src/Socialize.Api/Modules/Approvals/Handlers/GetApprovals.cs +++ b/backend/src/Socialize.Api/Modules/Approvals/Handlers/GetApprovals.cs @@ -61,7 +61,7 @@ public class GetApprovalsHandler( return; } - if (!accessScopeService.CanReviewContent(User, item.WorkspaceId, item.ClientId, item.ProjectId)) + if (!accessScopeService.CanReviewContent(User, item.WorkspaceId, item.ClientId, item.CampaignId)) { await SendForbiddenAsync(ct); return; diff --git a/backend/src/Socialize.Api/Modules/Approvals/Handlers/SubmitApprovalDecision.cs b/backend/src/Socialize.Api/Modules/Approvals/Handlers/SubmitApprovalDecision.cs index 3818bcf..720fd7e 100644 --- a/backend/src/Socialize.Api/Modules/Approvals/Handlers/SubmitApprovalDecision.cs +++ b/backend/src/Socialize.Api/Modules/Approvals/Handlers/SubmitApprovalDecision.cs @@ -64,7 +64,7 @@ public class SubmitApprovalDecisionHandler( } if (User?.Identity?.IsAuthenticated == true && - !accessScopeService.CanReviewContent(User, contentItem.WorkspaceId, contentItem.ClientId, contentItem.ProjectId)) + !accessScopeService.CanReviewContent(User, contentItem.WorkspaceId, contentItem.ClientId, contentItem.CampaignId)) { await SendForbiddenAsync(ct); return; diff --git a/backend/src/Socialize.Api/Modules/Assets/Handlers/CreateAssetRevision.cs b/backend/src/Socialize.Api/Modules/Assets/Handlers/CreateAssetRevision.cs index 60d164a..e42598d 100644 --- a/backend/src/Socialize.Api/Modules/Assets/Handlers/CreateAssetRevision.cs +++ b/backend/src/Socialize.Api/Modules/Assets/Handlers/CreateAssetRevision.cs @@ -51,7 +51,7 @@ public class CreateAssetRevisionHandler( .SingleOrDefaultAsync(candidate => candidate.Id == asset.ContentItemId, ct); if (contentItem is not null && - !accessScopeService.CanContributeToProject(User, contentItem.WorkspaceId, contentItem.ClientId, contentItem.ProjectId)) + !accessScopeService.CanContributeToCampaign(User, contentItem.WorkspaceId, contentItem.ClientId, contentItem.CampaignId)) { await SendForbiddenAsync(ct); return; diff --git a/backend/src/Socialize.Api/Modules/Assets/Handlers/CreateGoogleDriveAsset.cs b/backend/src/Socialize.Api/Modules/Assets/Handlers/CreateGoogleDriveAsset.cs index 6d1741f..e838af5 100644 --- a/backend/src/Socialize.Api/Modules/Assets/Handlers/CreateGoogleDriveAsset.cs +++ b/backend/src/Socialize.Api/Modules/Assets/Handlers/CreateGoogleDriveAsset.cs @@ -58,7 +58,7 @@ public class CreateGoogleDriveAssetHandler( return; } - if (!accessScopeService.CanContributeToProject(User, contentItem.WorkspaceId, contentItem.ClientId, contentItem.ProjectId)) + if (!accessScopeService.CanContributeToCampaign(User, contentItem.WorkspaceId, contentItem.ClientId, contentItem.CampaignId)) { await SendForbiddenAsync(ct); return; diff --git a/backend/src/Socialize.Api/Modules/Assets/Handlers/GetAssets.cs b/backend/src/Socialize.Api/Modules/Assets/Handlers/GetAssets.cs index 578bd86..04765c0 100644 --- a/backend/src/Socialize.Api/Modules/Assets/Handlers/GetAssets.cs +++ b/backend/src/Socialize.Api/Modules/Assets/Handlers/GetAssets.cs @@ -52,7 +52,7 @@ public class GetAssetsHandler( return; } - if (!accessScopeService.CanReviewContent(User, item.WorkspaceId, item.ClientId, item.ProjectId)) + if (!accessScopeService.CanReviewContent(User, item.WorkspaceId, item.ClientId, item.CampaignId)) { await SendForbiddenAsync(ct); return; diff --git a/backend/src/Socialize.Api/Modules/Projects/Data/Project.cs b/backend/src/Socialize.Api/Modules/Campaigns/Data/Campaign.cs similarity index 86% rename from backend/src/Socialize.Api/Modules/Projects/Data/Project.cs rename to backend/src/Socialize.Api/Modules/Campaigns/Data/Campaign.cs index f18a024..aafe569 100644 --- a/backend/src/Socialize.Api/Modules/Projects/Data/Project.cs +++ b/backend/src/Socialize.Api/Modules/Campaigns/Data/Campaign.cs @@ -1,6 +1,6 @@ -namespace Socialize.Api.Modules.Projects.Data; +namespace Socialize.Api.Modules.Campaigns.Data; -public class Project +public class Campaign { public Guid Id { get; init; } public Guid WorkspaceId { get; set; } diff --git a/backend/src/Socialize.Api/Modules/Campaigns/Data/CampaignModelConfiguration.cs b/backend/src/Socialize.Api/Modules/Campaigns/Data/CampaignModelConfiguration.cs new file mode 100644 index 0000000..a709d26 --- /dev/null +++ b/backend/src/Socialize.Api/Modules/Campaigns/Data/CampaignModelConfiguration.cs @@ -0,0 +1,27 @@ +using Microsoft.EntityFrameworkCore; + +namespace Socialize.Api.Modules.Campaigns.Data; + +public static class CampaignModelConfiguration +{ + public static ModelBuilder ConfigureCampaignsModule(this ModelBuilder modelBuilder) + { + modelBuilder.Entity(campaign => + { + campaign.ToTable("Campaigns"); + campaign.HasKey(x => x.Id); + campaign.Property(x => x.Name).HasMaxLength(256).IsRequired(); + campaign.Property(x => x.Description).HasMaxLength(4000); + campaign.Property(x => x.Notes).HasMaxLength(4000); + campaign.Property(x => x.Status).HasMaxLength(64).IsRequired(); + campaign.Property(x => x.CreatedAt) + .ValueGeneratedOnAdd() + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + campaign.HasIndex(x => new { x.ClientId, x.Name }).IsUnique(); + campaign.HasIndex(x => x.WorkspaceId); + campaign.HasIndex(x => x.ClientId); + }); + + return modelBuilder; + } +} diff --git a/backend/src/Socialize.Api/Modules/Campaigns/DependencyInjection.cs b/backend/src/Socialize.Api/Modules/Campaigns/DependencyInjection.cs new file mode 100644 index 0000000..fbb4521 --- /dev/null +++ b/backend/src/Socialize.Api/Modules/Campaigns/DependencyInjection.cs @@ -0,0 +1,12 @@ +using Socialize.Api.Modules.Campaigns.Data; + +namespace Socialize.Api.Modules.Campaigns; + +public static class DependencyInjection +{ + public static WebApplicationBuilder AddCampaignsModule( + this WebApplicationBuilder builder) + { + return builder; + } +} diff --git a/backend/src/Socialize.Api/Modules/Projects/Handlers/CreateProject.cs b/backend/src/Socialize.Api/Modules/Campaigns/Handlers/CreateCampaign.cs similarity index 69% rename from backend/src/Socialize.Api/Modules/Projects/Handlers/CreateProject.cs rename to backend/src/Socialize.Api/Modules/Campaigns/Handlers/CreateCampaign.cs index 0e9c7c5..9ef82ca 100644 --- a/backend/src/Socialize.Api/Modules/Projects/Handlers/CreateProject.cs +++ b/backend/src/Socialize.Api/Modules/Campaigns/Handlers/CreateCampaign.cs @@ -2,11 +2,11 @@ using FastEndpoints; using Microsoft.EntityFrameworkCore; using Socialize.Api.Data; using Socialize.Api.Infrastructure.Security; -using Socialize.Api.Modules.Projects.Data; +using Socialize.Api.Modules.Campaigns.Data; -namespace Socialize.Api.Modules.Projects.Handlers; +namespace Socialize.Api.Modules.Campaigns.Handlers; -public record CreateProjectRequest( +public record CreateCampaignRequest( Guid WorkspaceId, Guid ClientId, string Name, @@ -15,10 +15,10 @@ public record CreateProjectRequest( string? Description, string? Notes); -public class CreateProjectRequestValidator - : Validator +public class CreateCampaignRequestValidator + : Validator { - public CreateProjectRequestValidator() + public CreateCampaignRequestValidator() { RuleFor(x => x.WorkspaceId).NotEmpty(); RuleFor(x => x.ClientId).NotEmpty(); @@ -32,18 +32,18 @@ public class CreateProjectRequestValidator } } -public class CreateProjectHandler( +public class CreateCampaignHandler( AppDbContext dbContext, AccessScopeService accessScopeService) - : Endpoint + : Endpoint { public override void Configure() { - Post("/api/projects"); - Options(o => o.WithTags("Projects")); + Post("/api/campaigns"); + Options(o => o.WithTags("Campaigns")); } - public override async Task HandleAsync(CreateProjectRequest request, CancellationToken ct) + public override async Task HandleAsync(CreateCampaignRequest request, CancellationToken ct) { if (!accessScopeService.CanManageWorkspace(User, request.WorkspaceId)) { @@ -75,19 +75,19 @@ public class CreateProjectHandler( string normalizedName = request.Name.Trim(); - bool duplicateProject = await dbContext.Projects + bool duplicateCampaign = await dbContext.Campaigns .AnyAsync( - project => project.ClientId == request.ClientId && project.Name == normalizedName, + campaign => campaign.ClientId == request.ClientId && campaign.Name == normalizedName, ct); - if (duplicateProject) + if (duplicateCampaign) { - AddError(request => request.Name, "A project with this name already exists for the selected client."); + AddError(request => request.Name, "A campaign with this name already exists for the selected client."); await SendErrorsAsync(StatusCodes.Status409Conflict, ct); return; } - Project project = new() + Campaign campaign = new() { Id = Guid.NewGuid(), WorkspaceId = request.WorkspaceId, @@ -101,19 +101,19 @@ public class CreateProjectHandler( CreatedAt = DateTimeOffset.UtcNow, }; - dbContext.Projects.Add(project); + dbContext.Campaigns.Add(campaign); await dbContext.SaveChangesAsync(ct); - ProjectDto dto = new( - project.Id, - project.WorkspaceId, - project.ClientId, - project.Name, - project.Description, - project.Notes, - project.Status, - project.StartDate, - project.EndDate); + CampaignDto dto = new( + campaign.Id, + campaign.WorkspaceId, + campaign.ClientId, + campaign.Name, + campaign.Description, + campaign.Notes, + campaign.Status, + campaign.StartDate, + campaign.EndDate); await SendAsync(dto, StatusCodes.Status201Created, ct); } diff --git a/backend/src/Socialize.Api/Modules/Campaigns/Handlers/GetCampaigns.cs b/backend/src/Socialize.Api/Modules/Campaigns/Handlers/GetCampaigns.cs new file mode 100644 index 0000000..35d1103 --- /dev/null +++ b/backend/src/Socialize.Api/Modules/Campaigns/Handlers/GetCampaigns.cs @@ -0,0 +1,89 @@ +using FastEndpoints; +using Microsoft.EntityFrameworkCore; +using Socialize.Api.Data; +using Socialize.Api.Infrastructure.Security; +using Socialize.Api.Modules.Campaigns.Data; + +namespace Socialize.Api.Modules.Campaigns.Handlers; + +public record GetCampaignsRequest(Guid? WorkspaceId, Guid? ClientId); + +public record CampaignDto( + Guid Id, + Guid WorkspaceId, + Guid ClientId, + string Name, + string? Description, + string? Notes, + string Status, + DateTimeOffset StartDate, + DateTimeOffset EndDate); + +public class GetCampaignsHandler( + AppDbContext dbContext, + AccessScopeService accessScopeService) + : Endpoint> +{ + public override void Configure() + { + Get("/api/campaigns"); + Options(o => o.WithTags("Campaigns")); + } + + public override async Task HandleAsync(GetCampaignsRequest request, CancellationToken ct) + { + IQueryable query = dbContext.Campaigns.AsQueryable(); + + if (accessScopeService.IsManager(User)) + { + if (request.WorkspaceId.HasValue) + { + query = query.Where(campaign => campaign.WorkspaceId == request.WorkspaceId.Value); + } + } + else + { + IReadOnlyCollection workspaceScopeIds = User.GetWorkspaceScopeIds(); + IReadOnlyCollection clientScopeIds = User.GetClientScopeIds(); + IReadOnlyCollection campaignScopeIds = User.GetCampaignScopeIds(); + + query = query.Where(campaign => workspaceScopeIds.Contains(campaign.WorkspaceId)); + + if (clientScopeIds.Count > 0) + { + query = query.Where(campaign => clientScopeIds.Contains(campaign.ClientId)); + } + + if (campaignScopeIds.Count > 0) + { + query = query.Where(campaign => campaignScopeIds.Contains(campaign.Id)); + } + } + + if (request.ClientId.HasValue) + { + query = query.Where(campaign => campaign.ClientId == request.ClientId.Value); + } + + if (request.WorkspaceId.HasValue) + { + query = query.Where(campaign => campaign.WorkspaceId == request.WorkspaceId.Value); + } + + List campaigns = await query + .OrderBy(campaign => campaign.Name) + .Select(campaign => new CampaignDto( + campaign.Id, + campaign.WorkspaceId, + campaign.ClientId, + campaign.Name, + campaign.Description, + campaign.Notes, + campaign.Status, + campaign.StartDate, + campaign.EndDate)) + .ToListAsync(ct); + + await SendOkAsync(campaigns, ct); + } +} diff --git a/backend/src/Socialize.Api/Modules/Comments/Handlers/CreateComment.cs b/backend/src/Socialize.Api/Modules/Comments/Handlers/CreateComment.cs index ed85298..61489c6 100644 --- a/backend/src/Socialize.Api/Modules/Comments/Handlers/CreateComment.cs +++ b/backend/src/Socialize.Api/Modules/Comments/Handlers/CreateComment.cs @@ -51,7 +51,7 @@ public class CreateCommentHandler( return; } - if (!accessScopeService.CanReviewContent(User, contentItem.WorkspaceId, contentItem.ClientId, contentItem.ProjectId)) + if (!accessScopeService.CanReviewContent(User, contentItem.WorkspaceId, contentItem.ClientId, contentItem.CampaignId)) { await SendForbiddenAsync(ct); return; diff --git a/backend/src/Socialize.Api/Modules/Comments/Handlers/GetComments.cs b/backend/src/Socialize.Api/Modules/Comments/Handlers/GetComments.cs index 38e9bac..b07d601 100644 --- a/backend/src/Socialize.Api/Modules/Comments/Handlers/GetComments.cs +++ b/backend/src/Socialize.Api/Modules/Comments/Handlers/GetComments.cs @@ -44,7 +44,7 @@ public class GetCommentsHandler( return; } - if (!accessScopeService.CanReviewContent(User, item.WorkspaceId, item.ClientId, item.ProjectId)) + if (!accessScopeService.CanReviewContent(User, item.WorkspaceId, item.ClientId, item.CampaignId)) { await SendForbiddenAsync(ct); return; diff --git a/backend/src/Socialize.Api/Modules/Comments/Handlers/ResolveComment.cs b/backend/src/Socialize.Api/Modules/Comments/Handlers/ResolveComment.cs index 2cbe540..c1fb2ae 100644 --- a/backend/src/Socialize.Api/Modules/Comments/Handlers/ResolveComment.cs +++ b/backend/src/Socialize.Api/Modules/Comments/Handlers/ResolveComment.cs @@ -40,7 +40,7 @@ public class ResolveCommentHandler( } bool canResolve = accessScopeService.CanManageWorkspace(User, comment.WorkspaceId) - || accessScopeService.CanContributeToProject(User, contentItem.WorkspaceId, contentItem.ClientId, contentItem.ProjectId); + || accessScopeService.CanContributeToCampaign(User, contentItem.WorkspaceId, contentItem.ClientId, contentItem.CampaignId); if (!canResolve) { diff --git a/backend/src/Socialize.Api/Modules/ContentItems/Data/ContentItem.cs b/backend/src/Socialize.Api/Modules/ContentItems/Data/ContentItem.cs index f668627..d9e42bd 100644 --- a/backend/src/Socialize.Api/Modules/ContentItems/Data/ContentItem.cs +++ b/backend/src/Socialize.Api/Modules/ContentItems/Data/ContentItem.cs @@ -5,7 +5,7 @@ public class ContentItem public Guid Id { get; init; } public Guid WorkspaceId { get; set; } public Guid ClientId { get; set; } - public Guid ProjectId { get; set; } + public Guid CampaignId { get; set; } public required string Title { get; set; } public required string PublicationMessage { get; set; } public required string PublicationTargets { get; set; } diff --git a/backend/src/Socialize.Api/Modules/ContentItems/Data/ContentItemModelConfiguration.cs b/backend/src/Socialize.Api/Modules/ContentItems/Data/ContentItemModelConfiguration.cs index 3ed767c..04fabc9 100644 --- a/backend/src/Socialize.Api/Modules/ContentItems/Data/ContentItemModelConfiguration.cs +++ b/backend/src/Socialize.Api/Modules/ContentItems/Data/ContentItemModelConfiguration.cs @@ -21,7 +21,7 @@ public static class ContentItemModelConfiguration .HasDefaultValueSql("CURRENT_TIMESTAMP"); contentItem.HasIndex(x => x.WorkspaceId); contentItem.HasIndex(x => x.ClientId); - contentItem.HasIndex(x => x.ProjectId); + contentItem.HasIndex(x => x.CampaignId); }); modelBuilder.Entity(revision => diff --git a/backend/src/Socialize.Api/Modules/ContentItems/Handlers/CreateContentItem.cs b/backend/src/Socialize.Api/Modules/ContentItems/Handlers/CreateContentItem.cs index 5bcf0df..ab9a438 100644 --- a/backend/src/Socialize.Api/Modules/ContentItems/Handlers/CreateContentItem.cs +++ b/backend/src/Socialize.Api/Modules/ContentItems/Handlers/CreateContentItem.cs @@ -11,7 +11,7 @@ namespace Socialize.Api.Modules.ContentItems.Handlers; public record CreateContentItemRequest( Guid WorkspaceId, Guid ClientId, - Guid ProjectId, + Guid CampaignId, string Title, string PublicationMessage, string PublicationTargets, @@ -25,7 +25,7 @@ public class CreateContentItemRequestValidator { RuleFor(x => x.WorkspaceId).NotEmpty(); RuleFor(x => x.ClientId).NotEmpty(); - RuleFor(x => x.ProjectId).NotEmpty(); + RuleFor(x => x.CampaignId).NotEmpty(); RuleFor(x => x.Title).NotEmpty().MaximumLength(256); RuleFor(x => x.PublicationMessage).NotEmpty().MaximumLength(4000); RuleFor(x => x.PublicationTargets).NotEmpty().MaximumLength(512); @@ -47,7 +47,7 @@ public class CreateContentItemHandler( public override async Task HandleAsync(CreateContentItemRequest request, CancellationToken ct) { - if (!accessScopeService.CanContributeToProject(User, request.WorkspaceId, request.ClientId, request.ProjectId)) + if (!accessScopeService.CanContributeToCampaign(User, request.WorkspaceId, request.ClientId, request.CampaignId)) { await SendForbiddenAsync(ct); return; @@ -75,16 +75,16 @@ public class CreateContentItemHandler( return; } - bool projectExists = await dbContext.Projects + bool campaignExists = await dbContext.Campaigns .AnyAsync( - project => project.Id == request.ProjectId && - project.WorkspaceId == request.WorkspaceId && - project.ClientId == request.ClientId, + campaign => campaign.Id == request.CampaignId && + campaign.WorkspaceId == request.WorkspaceId && + campaign.ClientId == request.ClientId, ct); - if (!projectExists) + if (!campaignExists) { - AddError(request => request.ProjectId, "The selected project does not belong to the selected client."); + AddError(request => request.CampaignId, "The selected campaign does not belong to the selected client."); await SendErrorsAsync(StatusCodes.Status400BadRequest, ct); return; } @@ -94,7 +94,7 @@ public class CreateContentItemHandler( Id = Guid.NewGuid(), WorkspaceId = request.WorkspaceId, ClientId = request.ClientId, - ProjectId = request.ProjectId, + CampaignId = request.CampaignId, Title = request.Title.Trim(), PublicationMessage = request.PublicationMessage.Trim(), PublicationTargets = request.PublicationTargets.Trim(), @@ -138,7 +138,7 @@ public class CreateContentItemHandler( item.Id, item.WorkspaceId, item.ClientId, - item.ProjectId, + item.CampaignId, item.Title, item.PublicationMessage, item.PublicationTargets, diff --git a/backend/src/Socialize.Api/Modules/ContentItems/Handlers/CreateContentItemRevision.cs b/backend/src/Socialize.Api/Modules/ContentItems/Handlers/CreateContentItemRevision.cs index 4df6f19..651a3d2 100644 --- a/backend/src/Socialize.Api/Modules/ContentItems/Handlers/CreateContentItemRevision.cs +++ b/backend/src/Socialize.Api/Modules/ContentItems/Handlers/CreateContentItemRevision.cs @@ -50,7 +50,7 @@ public class CreateContentItemRevisionHandler( return; } - if (!accessScopeService.CanContributeToProject(User, item.WorkspaceId, item.ClientId, item.ProjectId)) + if (!accessScopeService.CanContributeToCampaign(User, item.WorkspaceId, item.ClientId, item.CampaignId)) { await SendForbiddenAsync(ct); return; diff --git a/backend/src/Socialize.Api/Modules/ContentItems/Handlers/GetContentItem.cs b/backend/src/Socialize.Api/Modules/ContentItems/Handlers/GetContentItem.cs index b4e088b..46464ff 100644 --- a/backend/src/Socialize.Api/Modules/ContentItems/Handlers/GetContentItem.cs +++ b/backend/src/Socialize.Api/Modules/ContentItems/Handlers/GetContentItem.cs @@ -10,7 +10,7 @@ public record ContentItemDetailDto( Guid Id, Guid WorkspaceId, Guid ClientId, - Guid ProjectId, + Guid CampaignId, string Title, string PublicationMessage, string PublicationTargets, @@ -42,7 +42,7 @@ public class GetContentItemHandler( candidate.Id, candidate.WorkspaceId, candidate.ClientId, - candidate.ProjectId, + candidate.CampaignId, candidate.Title, candidate.PublicationMessage, candidate.PublicationTargets, @@ -60,7 +60,7 @@ public class GetContentItemHandler( return; } - if (!accessScopeService.CanReviewContent(User, item.WorkspaceId, item.ClientId, item.ProjectId)) + if (!accessScopeService.CanReviewContent(User, item.WorkspaceId, item.ClientId, item.CampaignId)) { await SendForbiddenAsync(ct); return; diff --git a/backend/src/Socialize.Api/Modules/ContentItems/Handlers/GetContentItemRevisions.cs b/backend/src/Socialize.Api/Modules/ContentItems/Handlers/GetContentItemRevisions.cs index e862175..ed906f3 100644 --- a/backend/src/Socialize.Api/Modules/ContentItems/Handlers/GetContentItemRevisions.cs +++ b/backend/src/Socialize.Api/Modules/ContentItems/Handlers/GetContentItemRevisions.cs @@ -41,7 +41,7 @@ public class GetContentItemRevisionsHandler( return; } - if (!accessScopeService.CanReviewContent(User, item.WorkspaceId, item.ClientId, item.ProjectId)) + if (!accessScopeService.CanReviewContent(User, item.WorkspaceId, item.ClientId, item.CampaignId)) { await SendForbiddenAsync(ct); return; diff --git a/backend/src/Socialize.Api/Modules/ContentItems/Handlers/GetContentItems.cs b/backend/src/Socialize.Api/Modules/ContentItems/Handlers/GetContentItems.cs index b145e14..189d200 100644 --- a/backend/src/Socialize.Api/Modules/ContentItems/Handlers/GetContentItems.cs +++ b/backend/src/Socialize.Api/Modules/ContentItems/Handlers/GetContentItems.cs @@ -6,13 +6,13 @@ using Socialize.Api.Modules.ContentItems.Data; namespace Socialize.Api.Modules.ContentItems.Handlers; -public record GetContentItemsRequest(Guid? WorkspaceId, Guid? ClientId, Guid? ProjectId); +public record GetContentItemsRequest(Guid? WorkspaceId, Guid? ClientId, Guid? CampaignId); public record ContentItemDto( Guid Id, Guid WorkspaceId, Guid ClientId, - Guid ProjectId, + Guid CampaignId, string Title, string PublicationMessage, string PublicationTargets, @@ -41,7 +41,7 @@ public class GetContentItemsHandler( { IReadOnlyCollection workspaceScopeIds = User.GetWorkspaceScopeIds(); IReadOnlyCollection clientScopeIds = User.GetClientScopeIds(); - IReadOnlyCollection projectScopeIds = User.GetProjectScopeIds(); + IReadOnlyCollection campaignScopeIds = User.GetCampaignScopeIds(); query = query.Where(item => workspaceScopeIds.Contains(item.WorkspaceId)); @@ -50,9 +50,9 @@ public class GetContentItemsHandler( query = query.Where(item => clientScopeIds.Contains(item.ClientId)); } - if (projectScopeIds.Count > 0) + if (campaignScopeIds.Count > 0) { - query = query.Where(item => projectScopeIds.Contains(item.ProjectId)); + query = query.Where(item => campaignScopeIds.Contains(item.CampaignId)); } } @@ -61,9 +61,9 @@ public class GetContentItemsHandler( query = query.Where(item => item.WorkspaceId == request.WorkspaceId.Value); } - if (request.ProjectId.HasValue) + if (request.CampaignId.HasValue) { - query = query.Where(item => item.ProjectId == request.ProjectId.Value); + query = query.Where(item => item.CampaignId == request.CampaignId.Value); } if (request.ClientId.HasValue) @@ -78,7 +78,7 @@ public class GetContentItemsHandler( item.Id, item.WorkspaceId, item.ClientId, - item.ProjectId, + item.CampaignId, item.Title, item.PublicationMessage, item.PublicationTargets, diff --git a/backend/src/Socialize.Api/Modules/ContentItems/Handlers/UpdateContentItemStatus.cs b/backend/src/Socialize.Api/Modules/ContentItems/Handlers/UpdateContentItemStatus.cs index 9d2c3ac..d66a04e 100644 --- a/backend/src/Socialize.Api/Modules/ContentItems/Handlers/UpdateContentItemStatus.cs +++ b/backend/src/Socialize.Api/Modules/ContentItems/Handlers/UpdateContentItemStatus.cs @@ -145,7 +145,7 @@ public class UpdateContentItemStatusHandler( item.Id, item.WorkspaceId, item.ClientId, - item.ProjectId, + item.CampaignId, item.Title, item.PublicationMessage, item.PublicationTargets, diff --git a/backend/src/Socialize.Api/Modules/Feedback/Contracts/FeedbackDtos.cs b/backend/src/Socialize.Api/Modules/Feedback/Contracts/FeedbackDtos.cs index 18814b5..807f468 100644 --- a/backend/src/Socialize.Api/Modules/Feedback/Contracts/FeedbackDtos.cs +++ b/backend/src/Socialize.Api/Modules/Feedback/Contracts/FeedbackDtos.cs @@ -7,8 +7,8 @@ public record FeedbackContextDto( string? WorkspaceName, Guid? ClientId, string? ClientName, - Guid? ProjectId, - string? ProjectName, + Guid? CampaignId, + string? CampaignName, Guid? ContentItemId, string? ContentItemTitle); @@ -82,8 +82,8 @@ public static class FeedbackDtoMapper report.WorkspaceName, report.ClientId, report.ClientName, - report.ProjectId, - report.ProjectName, + report.CampaignId, + report.CampaignName, report.ContentItemId, report.ContentItemTitle), report.Screenshot is null diff --git a/backend/src/Socialize.Api/Modules/Feedback/Data/FeedbackModelConfiguration.cs b/backend/src/Socialize.Api/Modules/Feedback/Data/FeedbackModelConfiguration.cs index d56538a..4645e1e 100644 --- a/backend/src/Socialize.Api/Modules/Feedback/Data/FeedbackModelConfiguration.cs +++ b/backend/src/Socialize.Api/Modules/Feedback/Data/FeedbackModelConfiguration.cs @@ -20,7 +20,7 @@ public static class FeedbackModelConfiguration feedback.Property(x => x.AppVersion).HasMaxLength(128); feedback.Property(x => x.WorkspaceName).HasMaxLength(256); feedback.Property(x => x.ClientName).HasMaxLength(256); - feedback.Property(x => x.ProjectName).HasMaxLength(256); + feedback.Property(x => x.CampaignName).HasMaxLength(256); feedback.Property(x => x.ContentItemTitle).HasMaxLength(256); feedback.Property(x => x.CancellationReason).HasMaxLength(2000); feedback.Property(x => x.CreatedAt).ValueGeneratedOnAdd().HasDefaultValueSql("CURRENT_TIMESTAMP"); diff --git a/backend/src/Socialize.Api/Modules/Feedback/Data/FeedbackReport.cs b/backend/src/Socialize.Api/Modules/Feedback/Data/FeedbackReport.cs index ee9cd7f..2e2ef51 100644 --- a/backend/src/Socialize.Api/Modules/Feedback/Data/FeedbackReport.cs +++ b/backend/src/Socialize.Api/Modules/Feedback/Data/FeedbackReport.cs @@ -18,8 +18,8 @@ public class FeedbackReport public string? WorkspaceName { get; set; } public Guid? ClientId { get; set; } public string? ClientName { get; set; } - public Guid? ProjectId { get; set; } - public string? ProjectName { get; set; } + public Guid? CampaignId { get; set; } + public string? CampaignName { get; set; } public Guid? ContentItemId { get; set; } public string? ContentItemTitle { get; set; } public DateTimeOffset CreatedAt { get; set; } diff --git a/backend/src/Socialize.Api/Modules/Feedback/Handlers/SubmitFeedback.cs b/backend/src/Socialize.Api/Modules/Feedback/Handlers/SubmitFeedback.cs index fc3c671..17e6a3d 100644 --- a/backend/src/Socialize.Api/Modules/Feedback/Handlers/SubmitFeedback.cs +++ b/backend/src/Socialize.Api/Modules/Feedback/Handlers/SubmitFeedback.cs @@ -19,8 +19,8 @@ public record SubmitFeedbackRequest( string? WorkspaceName, Guid? ClientId, string? ClientName, - Guid? ProjectId, - string? ProjectName, + Guid? CampaignId, + string? CampaignName, Guid? ContentItemId, string? ContentItemTitle); @@ -36,7 +36,7 @@ public class SubmitFeedbackRequestValidator RuleFor(x => x.AppVersion).MaximumLength(128); RuleFor(x => x.WorkspaceName).MaximumLength(256); RuleFor(x => x.ClientName).MaximumLength(256); - RuleFor(x => x.ProjectName).MaximumLength(256); + RuleFor(x => x.CampaignName).MaximumLength(256); RuleFor(x => x.ContentItemTitle).MaximumLength(256); RuleFor(x => x.ViewportWidth).GreaterThan(0).When(x => x.ViewportWidth.HasValue); RuleFor(x => x.ViewportHeight).GreaterThan(0).When(x => x.ViewportHeight.HasValue); @@ -82,8 +82,8 @@ public class SubmitFeedbackHandler( WorkspaceName = NormalizeOptional(request.WorkspaceName), ClientId = request.ClientId, ClientName = NormalizeOptional(request.ClientName), - ProjectId = request.ProjectId, - ProjectName = NormalizeOptional(request.ProjectName), + CampaignId = request.CampaignId, + CampaignName = NormalizeOptional(request.CampaignName), ContentItemId = request.ContentItemId, ContentItemTitle = NormalizeOptional(request.ContentItemTitle), CreatedAt = now, diff --git a/backend/src/Socialize.Api/Modules/Identity/Handlers/GetCurrentUser.cs b/backend/src/Socialize.Api/Modules/Identity/Handlers/GetCurrentUser.cs index 6abbdf8..31bd0b4 100644 --- a/backend/src/Socialize.Api/Modules/Identity/Handlers/GetCurrentUser.cs +++ b/backend/src/Socialize.Api/Modules/Identity/Handlers/GetCurrentUser.cs @@ -50,8 +50,8 @@ public class GetCurrentUserQueryHandler( .Distinct() .ToList(); - List projectIds = claims - .Where(claim => claim.Type == KnownClaims.ProjectScope) + List campaignIds = claims + .Where(claim => claim.Type == KnownClaims.CampaignScope) .Select(claim => Guid.TryParse(claim.Value, out Guid id) ? id : Guid.Empty) .Where(id => id != Guid.Empty) .Distinct() @@ -64,7 +64,7 @@ public class GetCurrentUserQueryHandler( Persona = persona, AuthorizedWorkspaceIds = workspaceIds, AuthorizedClientIds = clientIds, - AuthorizedProjectIds = projectIds, + AuthorizedCampaignIds = campaignIds, Alias = userModel.Alias, PortraitUrl = userModel.PortraitUrl, Firstname = userModel.Firstname, diff --git a/backend/src/Socialize.Api/Modules/Identity/Models/UserDto.cs b/backend/src/Socialize.Api/Modules/Identity/Models/UserDto.cs index 5f64a2b..c565f37 100644 --- a/backend/src/Socialize.Api/Modules/Identity/Models/UserDto.cs +++ b/backend/src/Socialize.Api/Modules/Identity/Models/UserDto.cs @@ -7,7 +7,7 @@ public class UserDto public string? Persona { get; init; } public IList AuthorizedWorkspaceIds { get; init; } = []; public IList AuthorizedClientIds { get; init; } = []; - public IList AuthorizedProjectIds { get; init; } = []; + public IList AuthorizedCampaignIds { get; init; } = []; public string Username { get; init; } = null!; public string? Alias { get; init; } public string? PortraitUrl { get; init; } diff --git a/backend/src/Socialize.Api/Modules/Notifications/Handlers/GetNotifications.cs b/backend/src/Socialize.Api/Modules/Notifications/Handlers/GetNotifications.cs index 5fabdb8..aea4b69 100644 --- a/backend/src/Socialize.Api/Modules/Notifications/Handlers/GetNotifications.cs +++ b/backend/src/Socialize.Api/Modules/Notifications/Handlers/GetNotifications.cs @@ -46,7 +46,7 @@ public class GetNotificationsHandler( return; } - if (!accessScopeService.CanReviewContent(User, item.WorkspaceId, item.ClientId, item.ProjectId)) + if (!accessScopeService.CanReviewContent(User, item.WorkspaceId, item.ClientId, item.CampaignId)) { await SendForbiddenAsync(ct); return; diff --git a/backend/src/Socialize.Api/Modules/Projects/Data/ProjectModelConfiguration.cs b/backend/src/Socialize.Api/Modules/Projects/Data/ProjectModelConfiguration.cs deleted file mode 100644 index ab871f1..0000000 --- a/backend/src/Socialize.Api/Modules/Projects/Data/ProjectModelConfiguration.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Microsoft.EntityFrameworkCore; - -namespace Socialize.Api.Modules.Projects.Data; - -public static class ProjectModelConfiguration -{ - public static ModelBuilder ConfigureProjectsModule(this ModelBuilder modelBuilder) - { - modelBuilder.Entity(project => - { - project.ToTable("Projects"); - project.HasKey(x => x.Id); - project.Property(x => x.Name).HasMaxLength(256).IsRequired(); - project.Property(x => x.Description).HasMaxLength(4000); - project.Property(x => x.Notes).HasMaxLength(4000); - project.Property(x => x.Status).HasMaxLength(64).IsRequired(); - project.Property(x => x.CreatedAt) - .ValueGeneratedOnAdd() - .HasDefaultValueSql("CURRENT_TIMESTAMP"); - project.HasIndex(x => new { x.ClientId, x.Name }).IsUnique(); - project.HasIndex(x => x.WorkspaceId); - project.HasIndex(x => x.ClientId); - }); - - return modelBuilder; - } -} diff --git a/backend/src/Socialize.Api/Modules/Projects/DependencyInjection.cs b/backend/src/Socialize.Api/Modules/Projects/DependencyInjection.cs deleted file mode 100644 index a0db954..0000000 --- a/backend/src/Socialize.Api/Modules/Projects/DependencyInjection.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Socialize.Api.Modules.Projects.Data; - -namespace Socialize.Api.Modules.Projects; - -public static class DependencyInjection -{ - public static WebApplicationBuilder AddProjectsModule( - this WebApplicationBuilder builder) - { - return builder; - } -} diff --git a/backend/src/Socialize.Api/Modules/Projects/Handlers/GetProjects.cs b/backend/src/Socialize.Api/Modules/Projects/Handlers/GetProjects.cs deleted file mode 100644 index 2bbbcf2..0000000 --- a/backend/src/Socialize.Api/Modules/Projects/Handlers/GetProjects.cs +++ /dev/null @@ -1,89 +0,0 @@ -using FastEndpoints; -using Microsoft.EntityFrameworkCore; -using Socialize.Api.Data; -using Socialize.Api.Infrastructure.Security; -using Socialize.Api.Modules.Projects.Data; - -namespace Socialize.Api.Modules.Projects.Handlers; - -public record GetProjectsRequest(Guid? WorkspaceId, Guid? ClientId); - -public record ProjectDto( - Guid Id, - Guid WorkspaceId, - Guid ClientId, - string Name, - string? Description, - string? Notes, - string Status, - DateTimeOffset StartDate, - DateTimeOffset EndDate); - -public class GetProjectsHandler( - AppDbContext dbContext, - AccessScopeService accessScopeService) - : Endpoint> -{ - public override void Configure() - { - Get("/api/projects"); - Options(o => o.WithTags("Projects")); - } - - public override async Task HandleAsync(GetProjectsRequest request, CancellationToken ct) - { - IQueryable query = dbContext.Projects.AsQueryable(); - - if (accessScopeService.IsManager(User)) - { - if (request.WorkspaceId.HasValue) - { - query = query.Where(project => project.WorkspaceId == request.WorkspaceId.Value); - } - } - else - { - IReadOnlyCollection workspaceScopeIds = User.GetWorkspaceScopeIds(); - IReadOnlyCollection clientScopeIds = User.GetClientScopeIds(); - IReadOnlyCollection projectScopeIds = User.GetProjectScopeIds(); - - query = query.Where(project => workspaceScopeIds.Contains(project.WorkspaceId)); - - if (clientScopeIds.Count > 0) - { - query = query.Where(project => clientScopeIds.Contains(project.ClientId)); - } - - if (projectScopeIds.Count > 0) - { - query = query.Where(project => projectScopeIds.Contains(project.Id)); - } - } - - if (request.ClientId.HasValue) - { - query = query.Where(project => project.ClientId == request.ClientId.Value); - } - - if (request.WorkspaceId.HasValue) - { - query = query.Where(project => project.WorkspaceId == request.WorkspaceId.Value); - } - - List projects = await query - .OrderBy(project => project.Name) - .Select(project => new ProjectDto( - project.Id, - project.WorkspaceId, - project.ClientId, - project.Name, - project.Description, - project.Notes, - project.Status, - project.StartDate, - project.EndDate)) - .ToListAsync(ct); - - await SendOkAsync(projects, ct); - } -} diff --git a/backend/src/Socialize.Api/Program.cs b/backend/src/Socialize.Api/Program.cs index 5f9d68c..f437285 100644 --- a/backend/src/Socialize.Api/Program.cs +++ b/backend/src/Socialize.Api/Program.cs @@ -16,7 +16,7 @@ using Socialize.Api.Modules.ContentItems; using Socialize.Api.Modules.Feedback; using Socialize.Api.Modules.Identity; using Socialize.Api.Modules.Notifications; -using Socialize.Api.Modules.Projects; +using Socialize.Api.Modules.Campaigns; using Socialize.Api.Modules.Workspaces; @@ -64,7 +64,7 @@ builder.AddInfrastructureModule(); builder.AddIdentityModule(); builder.AddWorkspaceModule(); builder.AddClientsModule(); -builder.AddProjectsModule(); +builder.AddCampaignsModule(); builder.AddContentItemsModule(); builder.AddAssetsModule(); builder.AddCommentsModule(); diff --git "a/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/Microsoft.Build.Locator.dll" "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/Microsoft.Build.Locator.dll" new file mode 100755 index 0000000..13b1021 Binary files /dev/null and "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/Microsoft.Build.Locator.dll" differ diff --git "a/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.exe" "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.exe" new file mode 100755 index 0000000..00dd99f Binary files /dev/null and "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.exe" differ diff --git "a/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.exe.config" "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.exe.config" new file mode 100755 index 0000000..14c9184 --- /dev/null +++ "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.exe.config" @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git "a/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/Microsoft.IO.Redist.dll" "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/Microsoft.IO.Redist.dll" new file mode 100755 index 0000000..88e63d8 Binary files /dev/null and "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/Microsoft.IO.Redist.dll" differ diff --git "a/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/Newtonsoft.Json.dll" "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/Newtonsoft.Json.dll" new file mode 100755 index 0000000..1d035d6 Binary files /dev/null and "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/Newtonsoft.Json.dll" differ diff --git "a/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/System.Buffers.dll" "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/System.Buffers.dll" new file mode 100755 index 0000000..f2d83c5 Binary files /dev/null and "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/System.Buffers.dll" differ diff --git "a/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/System.Collections.Immutable.dll" "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/System.Collections.Immutable.dll" new file mode 100755 index 0000000..7594b2e Binary files /dev/null and "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/System.Collections.Immutable.dll" differ diff --git "a/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/System.CommandLine.dll" "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/System.CommandLine.dll" new file mode 100755 index 0000000..d0bbad5 Binary files /dev/null and "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/System.CommandLine.dll" differ diff --git "a/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/System.Memory.dll" "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/System.Memory.dll" new file mode 100755 index 0000000..4617199 Binary files /dev/null and "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/System.Memory.dll" differ diff --git "a/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/System.Numerics.Vectors.dll" "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/System.Numerics.Vectors.dll" new file mode 100755 index 0000000..0865972 Binary files /dev/null and "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/System.Numerics.Vectors.dll" differ diff --git "a/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/System.Runtime.CompilerServices.Unsafe.dll" "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/System.Runtime.CompilerServices.Unsafe.dll" new file mode 100755 index 0000000..c5ba4e4 Binary files /dev/null and "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/System.Runtime.CompilerServices.Unsafe.dll" differ diff --git "a/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/System.Threading.Tasks.Extensions.dll" "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/System.Threading.Tasks.Extensions.dll" new file mode 100755 index 0000000..eeec928 Binary files /dev/null and "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/System.Threading.Tasks.Extensions.dll" differ diff --git "a/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/cs/System.CommandLine.resources.dll" "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/cs/System.CommandLine.resources.dll" new file mode 100755 index 0000000..0be3757 Binary files /dev/null and "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/cs/System.CommandLine.resources.dll" differ diff --git "a/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/de/System.CommandLine.resources.dll" "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/de/System.CommandLine.resources.dll" new file mode 100755 index 0000000..bfed293 Binary files /dev/null and "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/de/System.CommandLine.resources.dll" differ diff --git "a/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/es/System.CommandLine.resources.dll" "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/es/System.CommandLine.resources.dll" new file mode 100755 index 0000000..5e1c416 Binary files /dev/null and "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/es/System.CommandLine.resources.dll" differ diff --git "a/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/fr/System.CommandLine.resources.dll" "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/fr/System.CommandLine.resources.dll" new file mode 100755 index 0000000..2916bdf Binary files /dev/null and "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/fr/System.CommandLine.resources.dll" differ diff --git "a/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/it/System.CommandLine.resources.dll" "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/it/System.CommandLine.resources.dll" new file mode 100755 index 0000000..1a55c94 Binary files /dev/null and "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/it/System.CommandLine.resources.dll" differ diff --git "a/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/ja/System.CommandLine.resources.dll" "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/ja/System.CommandLine.resources.dll" new file mode 100755 index 0000000..c1be153 Binary files /dev/null and "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/ja/System.CommandLine.resources.dll" differ diff --git "a/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/ko/System.CommandLine.resources.dll" "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/ko/System.CommandLine.resources.dll" new file mode 100755 index 0000000..bfcbbc6 Binary files /dev/null and "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/ko/System.CommandLine.resources.dll" differ diff --git "a/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/pl/System.CommandLine.resources.dll" "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/pl/System.CommandLine.resources.dll" new file mode 100755 index 0000000..b9efaec Binary files /dev/null and "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/pl/System.CommandLine.resources.dll" differ diff --git "a/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/pt-BR/System.CommandLine.resources.dll" "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/pt-BR/System.CommandLine.resources.dll" new file mode 100755 index 0000000..69612cb Binary files /dev/null and "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/pt-BR/System.CommandLine.resources.dll" differ diff --git "a/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/ru/System.CommandLine.resources.dll" "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/ru/System.CommandLine.resources.dll" new file mode 100755 index 0000000..042aaf8 Binary files /dev/null and "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/ru/System.CommandLine.resources.dll" differ diff --git "a/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/tr/System.CommandLine.resources.dll" "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/tr/System.CommandLine.resources.dll" new file mode 100755 index 0000000..629b98b Binary files /dev/null and "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/tr/System.CommandLine.resources.dll" differ diff --git "a/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/zh-Hans/System.CommandLine.resources.dll" "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/zh-Hans/System.CommandLine.resources.dll" new file mode 100755 index 0000000..ff8dacb Binary files /dev/null and "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/zh-Hans/System.CommandLine.resources.dll" differ diff --git "a/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/zh-Hant/System.CommandLine.resources.dll" "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/zh-Hant/System.CommandLine.resources.dll" new file mode 100755 index 0000000..9b9870a Binary files /dev/null and "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-net472/zh-Hant/System.CommandLine.resources.dll" differ diff --git "a/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-netcore/Microsoft.Build.Locator.dll" "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-netcore/Microsoft.Build.Locator.dll" new file mode 100755 index 0000000..cafcf21 Binary files /dev/null and "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-netcore/Microsoft.Build.Locator.dll" differ diff --git "a/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-netcore/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.deps.json" "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-netcore/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.deps.json" new file mode 100755 index 0000000..059c550 --- /dev/null +++ "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-netcore/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.deps.json" @@ -0,0 +1,260 @@ +{ + "runtimeTarget": { + "name": ".NETCoreApp,Version=v6.0", + "signature": "" + }, + "compilationOptions": {}, + "targets": { + ".NETCoreApp,Version=v6.0": { + "Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost/4.14.0-3.25262.10": { + "dependencies": { + "Microsoft.Build.Locator": "1.6.10", + "Microsoft.CodeAnalysis.NetAnalyzers": "8.0.0-preview.23468.1", + "Microsoft.CodeAnalysis.PerformanceSensitiveAnalyzers": "3.3.4-beta1.22504.1", + "Microsoft.DotNet.XliffTasks": "9.0.0-beta.25255.5", + "Microsoft.VisualStudio.Threading.Analyzers": "17.13.2", + "Newtonsoft.Json": "13.0.3", + "Roslyn.Diagnostics.Analyzers": "3.11.0-beta1.24081.1", + "System.Collections.Immutable": "9.0.0", + "System.CommandLine": "2.0.0-beta4.24528.1" + }, + "runtime": { + "Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.dll": {} + }, + "resources": { + "cs/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "cs" + }, + "de/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "de" + }, + "es/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "es" + }, + "fr/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "fr" + }, + "it/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "it" + }, + "ja/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "ja" + }, + "ko/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "ko" + }, + "pl/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "pl" + }, + "pt-BR/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "pt-BR" + }, + "ru/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "ru" + }, + "tr/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "tr" + }, + "zh-Hans/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "zh-Hans" + }, + "zh-Hant/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": { + "locale": "zh-Hant" + } + } + }, + "Microsoft.Build.Locator/1.6.10": { + "runtime": { + "lib/net6.0/Microsoft.Build.Locator.dll": { + "assemblyVersion": "1.0.0.0", + "fileVersion": "1.6.10.57384" + } + } + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers/3.11.0-beta1.24081.1": {}, + "Microsoft.CodeAnalysis.NetAnalyzers/8.0.0-preview.23468.1": {}, + "Microsoft.CodeAnalysis.PerformanceSensitiveAnalyzers/3.3.4-beta1.22504.1": {}, + "Microsoft.CodeAnalysis.PublicApiAnalyzers/3.11.0-beta1.24081.1": {}, + "Microsoft.DotNet.XliffTasks/9.0.0-beta.25255.5": {}, + "Microsoft.VisualStudio.Threading.Analyzers/17.13.2": {}, + "Newtonsoft.Json/13.0.3": { + "runtime": { + "lib/net6.0/Newtonsoft.Json.dll": { + "assemblyVersion": "13.0.0.0", + "fileVersion": "13.0.3.27908" + } + } + }, + "Roslyn.Diagnostics.Analyzers/3.11.0-beta1.24081.1": { + "dependencies": { + "Microsoft.CodeAnalysis.BannedApiAnalyzers": "3.11.0-beta1.24081.1", + "Microsoft.CodeAnalysis.PublicApiAnalyzers": "3.11.0-beta1.24081.1" + } + }, + "System.Collections.Immutable/9.0.0": { + "dependencies": { + "System.Memory": "4.5.5", + "System.Runtime.CompilerServices.Unsafe": "6.0.0" + }, + "runtime": { + "lib/netstandard2.0/System.Collections.Immutable.dll": { + "assemblyVersion": "9.0.0.0", + "fileVersion": "9.0.24.52809" + } + } + }, + "System.CommandLine/2.0.0-beta4.24528.1": { + "dependencies": { + "System.Memory": "4.5.5" + }, + "runtime": { + "lib/netstandard2.0/System.CommandLine.dll": { + "assemblyVersion": "2.0.0.0", + "fileVersion": "2.0.24.52801" + } + }, + "resources": { + "lib/netstandard2.0/cs/System.CommandLine.resources.dll": { + "locale": "cs" + }, + "lib/netstandard2.0/de/System.CommandLine.resources.dll": { + "locale": "de" + }, + "lib/netstandard2.0/es/System.CommandLine.resources.dll": { + "locale": "es" + }, + "lib/netstandard2.0/fr/System.CommandLine.resources.dll": { + "locale": "fr" + }, + "lib/netstandard2.0/it/System.CommandLine.resources.dll": { + "locale": "it" + }, + "lib/netstandard2.0/ja/System.CommandLine.resources.dll": { + "locale": "ja" + }, + "lib/netstandard2.0/ko/System.CommandLine.resources.dll": { + "locale": "ko" + }, + "lib/netstandard2.0/pl/System.CommandLine.resources.dll": { + "locale": "pl" + }, + "lib/netstandard2.0/pt-BR/System.CommandLine.resources.dll": { + "locale": "pt-BR" + }, + "lib/netstandard2.0/ru/System.CommandLine.resources.dll": { + "locale": "ru" + }, + "lib/netstandard2.0/tr/System.CommandLine.resources.dll": { + "locale": "tr" + }, + "lib/netstandard2.0/zh-Hans/System.CommandLine.resources.dll": { + "locale": "zh-Hans" + }, + "lib/netstandard2.0/zh-Hant/System.CommandLine.resources.dll": { + "locale": "zh-Hant" + } + } + }, + "System.Memory/4.5.5": {}, + "System.Runtime.CompilerServices.Unsafe/6.0.0": {} + } + }, + "libraries": { + "Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost/4.14.0-3.25262.10": { + "type": "project", + "serviceable": false, + "sha512": "" + }, + "Microsoft.Build.Locator/1.6.10": { + "type": "package", + "serviceable": true, + "sha512": "sha512-DJhCkTGqy1LMJzEmG/2qxRTMHwdPc3WdVoGQI5o5mKHVo4dsHrCMLIyruwU/NSvPNSdvONlaf7jdFXnAMuxAuA==", + "path": "microsoft.build.locator/1.6.10", + "hashPath": "microsoft.build.locator.1.6.10.nupkg.sha512" + }, + "Microsoft.CodeAnalysis.BannedApiAnalyzers/3.11.0-beta1.24081.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-DH6L3rsbjppLrHM2l2/NKbnMaYd0NFHx2pjZaFdrVcRkONrV3i9FHv6Id8Dp6/TmjhXQsJVJJFbhhjkpuP1xxg==", + "path": "microsoft.codeanalysis.bannedapianalyzers/3.11.0-beta1.24081.1", + "hashPath": "microsoft.codeanalysis.bannedapianalyzers.3.11.0-beta1.24081.1.nupkg.sha512" + }, + "Microsoft.CodeAnalysis.NetAnalyzers/8.0.0-preview.23468.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-ZhIvyxmUCqb8OiU/VQfxfuAmIB4lQsjqhMVYKeoyxzSI+d7uR5Pzx3ZKoaIhPizQ15wa4lnyD6wg3TnSJ6P4LA==", + "path": "microsoft.codeanalysis.netanalyzers/8.0.0-preview.23468.1", + "hashPath": "microsoft.codeanalysis.netanalyzers.8.0.0-preview.23468.1.nupkg.sha512" + }, + "Microsoft.CodeAnalysis.PerformanceSensitiveAnalyzers/3.3.4-beta1.22504.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-2XRlqPAzVke7Sb80+UqaC7o57OwfK+tIr+aIOxrx41RWDMeR2SBUW7kL4sd6hfLFfBNsLo3W5PT+UwfvwPaOzA==", + "path": "microsoft.codeanalysis.performancesensitiveanalyzers/3.3.4-beta1.22504.1", + "hashPath": "microsoft.codeanalysis.performancesensitiveanalyzers.3.3.4-beta1.22504.1.nupkg.sha512" + }, + "Microsoft.CodeAnalysis.PublicApiAnalyzers/3.11.0-beta1.24081.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-3bYGBihvoNO0rhCOG1U9O50/4Q8suZ+glHqQLIAcKvnodSnSW+dYWYzTNb1UbS8pUS8nAUfxSFMwuMup/G5DtQ==", + "path": "microsoft.codeanalysis.publicapianalyzers/3.11.0-beta1.24081.1", + "hashPath": "microsoft.codeanalysis.publicapianalyzers.3.11.0-beta1.24081.1.nupkg.sha512" + }, + "Microsoft.DotNet.XliffTasks/9.0.0-beta.25255.5": { + "type": "package", + "serviceable": true, + "sha512": "sha512-bb0fZB5ViPscdfYeWlmtyXJMzNkgcpkV5RWmXktfV9lwIUZgNZmFotUXrdcTyZzrN7v1tQK/Y6BGnbkP9gEsXg==", + "path": "microsoft.dotnet.xlifftasks/9.0.0-beta.25255.5", + "hashPath": "microsoft.dotnet.xlifftasks.9.0.0-beta.25255.5.nupkg.sha512" + }, + "Microsoft.VisualStudio.Threading.Analyzers/17.13.2": { + "type": "package", + "serviceable": true, + "sha512": "sha512-Qcd8IlaTXZVq3wolBnzby1P7kWihdWaExtD8riumiKuG1sHa8EgjV/o70TMjTaeUMhomBbhfdC9OPwAHoZfnjQ==", + "path": "microsoft.visualstudio.threading.analyzers/17.13.2", + "hashPath": "microsoft.visualstudio.threading.analyzers.17.13.2.nupkg.sha512" + }, + "Newtonsoft.Json/13.0.3": { + "type": "package", + "serviceable": true, + "sha512": "sha512-HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==", + "path": "newtonsoft.json/13.0.3", + "hashPath": "newtonsoft.json.13.0.3.nupkg.sha512" + }, + "Roslyn.Diagnostics.Analyzers/3.11.0-beta1.24081.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-reHqZCDKifA+DURcL8jUfYkMGL4FpgNt5LI0uWTS6IpM8kKVbu/kO8byZsqfhBu4wUzT3MBDcoMfzhZPdENIpg==", + "path": "roslyn.diagnostics.analyzers/3.11.0-beta1.24081.1", + "hashPath": "roslyn.diagnostics.analyzers.3.11.0-beta1.24081.1.nupkg.sha512" + }, + "System.Collections.Immutable/9.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-QhkXUl2gNrQtvPmtBTQHb0YsUrDiDQ2QS09YbtTTiSjGcf7NBqtYbrG/BE06zcBPCKEwQGzIv13IVdXNOSub2w==", + "path": "system.collections.immutable/9.0.0", + "hashPath": "system.collections.immutable.9.0.0.nupkg.sha512" + }, + "System.CommandLine/2.0.0-beta4.24528.1": { + "type": "package", + "serviceable": true, + "sha512": "sha512-Xt8tsSU8yd0ZpbT9gl5DAwkMYWLo8PV1fq2R/belrUbHVVOIKqhLfbWksbdknUDpmzMHZenBtD6AGAp9uJTa2w==", + "path": "system.commandline/2.0.0-beta4.24528.1", + "hashPath": "system.commandline.2.0.0-beta4.24528.1.nupkg.sha512" + }, + "System.Memory/4.5.5": { + "type": "package", + "serviceable": true, + "sha512": "sha512-XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==", + "path": "system.memory/4.5.5", + "hashPath": "system.memory.4.5.5.nupkg.sha512" + }, + "System.Runtime.CompilerServices.Unsafe/6.0.0": { + "type": "package", + "serviceable": true, + "sha512": "sha512-/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==", + "path": "system.runtime.compilerservices.unsafe/6.0.0", + "hashPath": "system.runtime.compilerservices.unsafe.6.0.0.nupkg.sha512" + } + } +} \ No newline at end of file diff --git "a/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-netcore/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.dll" "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-netcore/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.dll" new file mode 100755 index 0000000..993b54f Binary files /dev/null and "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-netcore/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.dll" differ diff --git "a/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-netcore/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.dll.config" "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-netcore/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.dll.config" new file mode 100755 index 0000000..f78385c --- /dev/null +++ "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-netcore/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.dll.config" @@ -0,0 +1,659 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git "a/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-netcore/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.runtimeconfig.json" "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-netcore/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.runtimeconfig.json" new file mode 100755 index 0000000..9a67d63 --- /dev/null +++ "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-netcore/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.runtimeconfig.json" @@ -0,0 +1,13 @@ +{ + "runtimeOptions": { + "tfm": "net6.0", + "framework": { + "name": "Microsoft.NETCore.App", + "version": "6.0.0" + }, + "rollForward": "Major", + "configProperties": { + "System.Reflection.Metadata.MetadataUpdater.IsSupported": false + } + } +} \ No newline at end of file diff --git "a/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-netcore/Newtonsoft.Json.dll" "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-netcore/Newtonsoft.Json.dll" new file mode 100755 index 0000000..87bf9aa Binary files /dev/null and "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-netcore/Newtonsoft.Json.dll" differ diff --git "a/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-netcore/System.Collections.Immutable.dll" "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-netcore/System.Collections.Immutable.dll" new file mode 100755 index 0000000..b182127 Binary files /dev/null and "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-netcore/System.Collections.Immutable.dll" differ diff --git "a/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-netcore/System.CommandLine.dll" "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-netcore/System.CommandLine.dll" new file mode 100755 index 0000000..d0bbad5 Binary files /dev/null and "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-netcore/System.CommandLine.dll" differ diff --git "a/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-netcore/cs/System.CommandLine.resources.dll" "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-netcore/cs/System.CommandLine.resources.dll" new file mode 100755 index 0000000..0be3757 Binary files /dev/null and "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-netcore/cs/System.CommandLine.resources.dll" differ diff --git "a/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-netcore/de/System.CommandLine.resources.dll" "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-netcore/de/System.CommandLine.resources.dll" new file mode 100755 index 0000000..bfed293 Binary files /dev/null and "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-netcore/de/System.CommandLine.resources.dll" differ diff --git "a/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-netcore/es/System.CommandLine.resources.dll" "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-netcore/es/System.CommandLine.resources.dll" new file mode 100755 index 0000000..5e1c416 Binary files /dev/null and "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-netcore/es/System.CommandLine.resources.dll" differ diff --git "a/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-netcore/fr/System.CommandLine.resources.dll" "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-netcore/fr/System.CommandLine.resources.dll" new file mode 100755 index 0000000..2916bdf Binary files /dev/null and "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-netcore/fr/System.CommandLine.resources.dll" differ diff --git "a/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-netcore/it/System.CommandLine.resources.dll" "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-netcore/it/System.CommandLine.resources.dll" new file mode 100755 index 0000000..1a55c94 Binary files /dev/null and "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-netcore/it/System.CommandLine.resources.dll" differ diff --git "a/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-netcore/ja/System.CommandLine.resources.dll" "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-netcore/ja/System.CommandLine.resources.dll" new file mode 100755 index 0000000..c1be153 Binary files /dev/null and "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-netcore/ja/System.CommandLine.resources.dll" differ diff --git "a/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-netcore/ko/System.CommandLine.resources.dll" "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-netcore/ko/System.CommandLine.resources.dll" new file mode 100755 index 0000000..bfcbbc6 Binary files /dev/null and "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-netcore/ko/System.CommandLine.resources.dll" differ diff --git "a/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-netcore/pl/System.CommandLine.resources.dll" "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-netcore/pl/System.CommandLine.resources.dll" new file mode 100755 index 0000000..b9efaec Binary files /dev/null and "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-netcore/pl/System.CommandLine.resources.dll" differ diff --git "a/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-netcore/pt-BR/System.CommandLine.resources.dll" "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-netcore/pt-BR/System.CommandLine.resources.dll" new file mode 100755 index 0000000..69612cb Binary files /dev/null and "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-netcore/pt-BR/System.CommandLine.resources.dll" differ diff --git "a/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-netcore/ru/System.CommandLine.resources.dll" "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-netcore/ru/System.CommandLine.resources.dll" new file mode 100755 index 0000000..042aaf8 Binary files /dev/null and "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-netcore/ru/System.CommandLine.resources.dll" differ diff --git "a/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-netcore/tr/System.CommandLine.resources.dll" "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-netcore/tr/System.CommandLine.resources.dll" new file mode 100755 index 0000000..629b98b Binary files /dev/null and "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-netcore/tr/System.CommandLine.resources.dll" differ diff --git "a/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-netcore/zh-Hans/System.CommandLine.resources.dll" "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-netcore/zh-Hans/System.CommandLine.resources.dll" new file mode 100755 index 0000000..ff8dacb Binary files /dev/null and "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-netcore/zh-Hans/System.CommandLine.resources.dll" differ diff --git "a/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-netcore/zh-Hant/System.CommandLine.resources.dll" "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-netcore/zh-Hant/System.CommandLine.resources.dll" new file mode 100755 index 0000000..9b9870a Binary files /dev/null and "b/backend/src/Socialize.Api/bin\\Debug/net10.0/BuildHost-netcore/zh-Hant/System.CommandLine.resources.dll" differ diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index ed17e10..d98f7ef 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -31,7 +31,7 @@ Composition registers: - web services and auth in `DependencyInjection.cs` - infrastructure in `Infrastructure/DependencyInjection.cs` -- domain modules for Identity, Workspaces, Clients, Projects, ContentItems, Assets, Comments, Approvals, Notifications, and Feedback +- domain modules for Identity, Workspaces, Clients, Campaigns, ContentItems, Assets, Comments, Approvals, Notifications, and Feedback ## Data Ownership diff --git a/docs/FEATURES/product-feedback.md b/docs/FEATURES/product-feedback.md index 93ba378..226f2be 100644 --- a/docs/FEATURES/product-feedback.md +++ b/docs/FEATURES/product-feedback.md @@ -100,7 +100,7 @@ Each report should capture useful debugging context automatically when available - current app URL/path - active workspace id/name - active client id/name -- active project id/name +- active campaign id/name - active content item id/title - browser user agent - viewport size diff --git a/docs/TASKS/campaigns/001-rename-projects-to-campaigns.md b/docs/TASKS/campaigns/001-rename-projects-to-campaigns.md new file mode 100644 index 0000000..2226b8e --- /dev/null +++ b/docs/TASKS/campaigns/001-rename-projects-to-campaigns.md @@ -0,0 +1,25 @@ +# Rename Projects To Campaigns + +## Goal + +Align the codebase terminology with the product language by replacing the `Project` domain surface with `Campaign`. + +## Relevant Specs + +- `docs/product/glossary.md` +- `docs/ARCHITECTURE.md` + +## Scope + +- Rename backend module, entity, DTOs, handlers, EF configuration, and route/tag names from projects to campaigns. +- Update content item and access-scope references that point at the renamed campaign concept. +- Update frontend feature naming and API calls where they still refer to projects. +- Update OpenAPI snapshots if backend contracts change and the backend can run. + +## Validation + +```bash +dotnet build backend/Socialize.slnx +dotnet test backend/Socialize.slnx +cd frontend && npm run build +``` diff --git a/docs/TASKS/product-feedback/001-backend-feedback-foundation.md b/docs/TASKS/product-feedback/001-backend-feedback-foundation.md index 989e1f4..e5d56b3 100644 --- a/docs/TASKS/product-feedback/001-backend-feedback-foundation.md +++ b/docs/TASKS/product-feedback/001-backend-feedback-foundation.md @@ -17,7 +17,7 @@ Add the backend foundation for product feedback reports. - type: `Bug`, `Suggestion`, `Request` - status: `New`, `Planned`, `Resolved`, `Won't Do`, `Cancelled` - Add `DbSet` entries and module configuration to `AppDbContext`. -- Capture reporter id, reporter display fields, submitted route, browser metadata, viewport size, app version if available, and optional workspace/client/project/content context. +- Capture reporter id, reporter display fields, submitted route, browser metadata, viewport size, app version if available, and optional workspace/client/campaign/content context. - Add API endpoints for: - submit feedback - list current user's feedback diff --git a/docs/product/glossary.md b/docs/product/glossary.md index d15628d..b452183 100644 --- a/docs/product/glossary.md +++ b/docs/product/glossary.md @@ -29,6 +29,10 @@ An agency is above the workspace level in the business model. Business, brand, or organization represented by a workspace and participating in review and approval flows. +## Campaign + +Client-owned body of content work that groups related content items, notes, and timelines. + ## Content Item Primary reviewable unit in the system. Contains metadata, copy, due dates, networks, channels, and linked assets. diff --git a/frontend/.env.development b/frontend/.env.development index b4bf1c3..be051ea 100644 --- a/frontend/.env.development +++ b/frontend/.env.development @@ -1,4 +1,4 @@ -VITE_API_URL=http://192.168.1.2:5080 +VITE_API_URL=http://192.168.1.17:5080 VITE_STRIPE_API_KEY=pk_test_51OoveVDrRyqXtNdB2st1NgA8WQA9rhgGaf3q7bCpAOoQyyRS30HMCzGeHba7meVGCSPfb1BVWmOTmFOcr9MkKf5H00bLu5MqsS VITE_GOOGLE_CLIENT_ID=213344094492-9dbaet2gaschju3hj1sgv1umk0qpd833.apps.googleusercontent.com VITE_FACEBOOK_APP_ID=1076433907621883 diff --git a/frontend/docs/claims-and-roles.md b/frontend/docs/claims-and-roles.md new file mode 100644 index 0000000..478b725 --- /dev/null +++ b/frontend/docs/claims-and-roles.md @@ -0,0 +1,16 @@ +# Claims and Roles Guidelines + +To ensure consistency across the application, all claim and role values MUST be in lowercase. + +## Roles +The following roles are currently used in the system: +- `administrator` +- `manager` +- `client` +- `provider` +- `developer` + +## Implementation Notes +- **Processing**: The `authStore.js` automatically converts all roles extracted from JWT tokens to lowercase. +- **Comparisons**: All checks (e.g., `authStore.hasAnyRole(['role-name'])` or `meta: { roles: ['role-name'] }`) should use lowercase strings. +- **Routing**: Route guards in `router.js` expect lowercase role names in the `meta.roles` field. diff --git a/frontend/src/api/schema.d.ts b/frontend/src/api/schema.d.ts index dcc2067..b188542 100644 --- a/frontend/src/api/schema.d.ts +++ b/frontend/src/api/schema.d.ts @@ -100,22 +100,6 @@ export interface paths { patch?: never; trace?: never; }; - "/api/projects": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - get: operations["SocializeApiModulesProjectsHandlersGetProjectsHandler"]; - put?: never; - post: operations["SocializeApiModulesProjectsHandlersCreateProjectHandler"]; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; "/api/notifications": { parameters: { query?: never; @@ -772,6 +756,22 @@ export interface paths { patch?: never; trace?: never; }; + "/api/campaigns": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["SocializeApiModulesCampaignsHandlersGetCampaignsHandler"]; + put?: never; + post: operations["SocializeApiModulesCampaignsHandlersCreateCampaignHandler"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/api/assets/{id}/revisions": { parameters: { query?: never; @@ -952,36 +952,6 @@ export interface components { /** Format: int32 */ requiredApproverCount?: number; }; - SocializeApiModulesProjectsHandlersProjectDto: { - /** Format: guid */ - id?: string; - /** Format: guid */ - workspaceId?: string; - /** Format: guid */ - clientId?: string; - name?: string; - description?: string | null; - notes?: string | null; - status?: string; - /** Format: date-time */ - startDate?: string; - /** Format: date-time */ - endDate?: string; - }; - SocializeApiModulesProjectsHandlersCreateProjectRequest: { - /** Format: guid */ - workspaceId: string; - /** Format: guid */ - clientId: string; - name: string; - /** Format: date-time */ - startDate: string; - /** Format: date-time */ - endDate: string; - description?: string | null; - notes?: string | null; - }; - SocializeApiModulesProjectsHandlersGetProjectsRequest: Record; SocializeApiModulesNotificationsHandlersNotificationEventDto: { /** Format: guid */ id?: string; @@ -1041,7 +1011,7 @@ export interface components { persona?: string | null; authorizedWorkspaceIds?: string[]; authorizedClientIds?: string[]; - authorizedProjectIds?: string[]; + authorizedCampaignIds?: string[]; username?: string; alias?: string | null; portraitUrl?: string | null; @@ -1176,8 +1146,8 @@ export interface components { clientId?: string | null; clientName?: string | null; /** Format: guid */ - projectId?: string | null; - projectName?: string | null; + campaignId?: string | null; + campaignName?: string | null; /** Format: guid */ contentItemId?: string | null; contentItemTitle?: string | null; @@ -1217,8 +1187,8 @@ export interface components { clientId?: string | null; clientName?: string | null; /** Format: guid */ - projectId?: string | null; - projectName?: string | null; + campaignId?: string | null; + campaignName?: string | null; /** Format: guid */ contentItemId?: string | null; contentItemTitle?: string | null; @@ -1236,7 +1206,7 @@ export interface components { /** Format: guid */ clientId?: string; /** Format: guid */ - projectId?: string; + campaignId?: string; title?: string; publicationMessage?: string; publicationTargets?: string; @@ -1254,7 +1224,7 @@ export interface components { /** Format: guid */ clientId: string; /** Format: guid */ - projectId: string; + campaignId: string; title: string; publicationMessage: string; publicationTargets: string; @@ -1295,7 +1265,7 @@ export interface components { /** Format: guid */ clientId?: string; /** Format: guid */ - projectId?: string; + campaignId?: string; title?: string; publicationMessage?: string; publicationTargets?: string; @@ -1383,6 +1353,36 @@ export interface components { primaryContactEmail?: string | null; primaryContactPortraitUrl?: string | null; }; + SocializeApiModulesCampaignsHandlersCampaignDto: { + /** Format: guid */ + id?: string; + /** Format: guid */ + workspaceId?: string; + /** Format: guid */ + clientId?: string; + name?: string; + description?: string | null; + notes?: string | null; + status?: string; + /** Format: date-time */ + startDate?: string; + /** Format: date-time */ + endDate?: string; + }; + SocializeApiModulesCampaignsHandlersCreateCampaignRequest: { + /** Format: guid */ + workspaceId: string; + /** Format: guid */ + clientId: string; + name: string; + /** Format: date-time */ + startDate: string; + /** Format: date-time */ + endDate: string; + description?: string | null; + notes?: string | null; + }; + SocializeApiModulesCampaignsHandlersGetCampaignsRequest: Record; SocializeApiModulesAssetsHandlersAssetRevisionDto: { /** Format: guid */ id?: string; @@ -1778,76 +1778,6 @@ export interface operations { }; }; }; - SocializeApiModulesProjectsHandlersGetProjectsHandler: { - parameters: { - query?: { - workspaceId?: string | null; - clientId?: string | null; - }; - header?: never; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description Success */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["SocializeApiModulesProjectsHandlersProjectDto"][]; - }; - }; - /** @description Unauthorized */ - 401: { - headers: { - [name: string]: unknown; - }; - content?: never; - }; - }; - }; - SocializeApiModulesProjectsHandlersCreateProjectHandler: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["SocializeApiModulesProjectsHandlersCreateProjectRequest"]; - }; - }; - responses: { - /** @description Success */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["SocializeApiModulesProjectsHandlersProjectDto"]; - }; - }; - /** @description Bad Request */ - 400: { - headers: { - [name: string]: unknown; - }; - content: { - "application/problem+json": components["schemas"]["FastEndpointsErrorResponse"]; - }; - }; - /** @description Unauthorized */ - 401: { - headers: { - [name: string]: unknown; - }; - content?: never; - }; - }; - }; SocializeApiModulesNotificationsHandlersGetNotificationsHandler: { parameters: { query?: { @@ -2936,7 +2866,7 @@ export interface operations { query?: { workspaceId?: string | null; clientId?: string | null; - projectId?: string | null; + campaignId?: string | null; }; header?: never; path?: never; @@ -3395,6 +3325,76 @@ export interface operations { }; }; }; + SocializeApiModulesCampaignsHandlersGetCampaignsHandler: { + parameters: { + query?: { + workspaceId?: string | null; + clientId?: string | null; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Success */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SocializeApiModulesCampaignsHandlersCampaignDto"][]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + SocializeApiModulesCampaignsHandlersCreateCampaignHandler: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["SocializeApiModulesCampaignsHandlersCreateCampaignRequest"]; + }; + }; + responses: { + /** @description Success */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SocializeApiModulesCampaignsHandlersCampaignDto"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/problem+json": components["schemas"]["FastEndpointsErrorResponse"]; + }; + }; + /** @description Unauthorized */ + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; SocializeApiModulesAssetsHandlersCreateAssetRevisionHandler: { parameters: { query?: never; diff --git a/frontend/src/features/auth/stores/authStore.js b/frontend/src/features/auth/stores/authStore.js index 5abada1..c68efa9 100644 --- a/frontend/src/features/auth/stores/authStore.js +++ b/frontend/src/features/auth/stores/authStore.js @@ -7,294 +7,295 @@ import { jwtDecode } from 'jwt-decode'; import { formatDuration } from '@/internal_time_ago.js'; export const useAuthStore = defineStore('auth', () => { - const clientApi = useClient(); - const router = useRouter(); + const clientApi = useClient(); + const router = useRouter(); - const isRefreshing = ref(false); - let refreshPromise = null; + const isRefreshing = ref(false); + let refreshPromise = null; - const accessToken = useSessionStorage('auth-accessToken', undefined); - const refreshToken = useSessionStorage('auth-refreshToken', undefined); - const tokenClaims = useSessionStorage('auth-tokenClaims', null, { - serializer: { - read: v => (v ? JSON.parse(v) : null), - write: v => (v ? JSON.stringify(v) : null), - }, - }); + const accessToken = useSessionStorage('auth-accessToken', undefined); + const refreshToken = useSessionStorage('auth-refreshToken', undefined); + const tokenClaims = useSessionStorage('auth-tokenClaims', null, { + serializer: { + read: v => (v ? JSON.parse(v) : null), + write: v => (v ? JSON.stringify(v) : null), + }, + }); - const isAuthenticated = computed(() => !!accessToken.value); - const userId = computed(() => tokenClaims.value?.sub); - const userRoles = computed(() => { - const claims = tokenClaims.value ?? {}; - const candidates = [ - claims.role, - claims.roles, - claims['http://schemas.microsoft.com/ws/2008/06/identity/claims/role'], - ].flatMap(value => Array.isArray(value) ? value : value ? [value] : []); + const isAuthenticated = computed(() => !!accessToken.value); + const userId = computed(() => tokenClaims.value?.sub); + const userRoles = computed(() => { + const claims = tokenClaims.value ?? {}; + const candidates = [ + claims.role, + claims.roles, + claims['http://schemas.microsoft.com/ws/2008/06/identity/claims/role'], + ].flatMap(value => Array.isArray(value) ? value : value ? [value] : []) + .map(v => v.toLowerCase()); - return [...new Set(candidates)]; - }); - const persona = computed(() => tokenClaims.value?.persona ?? null); - const isManager = computed(() => userRoles.value.includes('Administrator') || userRoles.value.includes('Manager')); - const isClient = computed(() => userRoles.value.includes('Client')); - const isProvider = computed(() => userRoles.value.includes('Provider')); + return [...new Set(candidates)]; + }); + const persona = computed(() => tokenClaims.value?.persona ?? null); + const isManager = computed(() => userRoles.value.includes('administrator') || userRoles.value.includes('manager')); + const isClient = computed(() => userRoles.value.includes('client')); + const isProvider = computed(() => userRoles.value.includes('provider')); - function updateTokens(data) { - if (!data?.accessToken || !data?.refreshToken) { - throw new Error('Invalid token data'); - } - accessToken.value = data.accessToken; - refreshToken.value = data.refreshToken; - const claims = getClaimsFromToken(data.accessToken); - tokenClaims.value = claims; - console.log('Tokens updated, user ID:', claims?.sub); + function updateTokens(data) { + if (!data?.accessToken || !data?.refreshToken) { + throw new Error('Invalid token data'); + } + accessToken.value = data.accessToken; + refreshToken.value = data.refreshToken; + const claims = getClaimsFromToken(data.accessToken); + tokenClaims.value = claims; + console.log('Tokens updated, user ID:', claims?.sub); + } + + function cleanTokens() { + console.log('cleanTokens called - clearing stored tokens'); + accessToken.value = undefined; + refreshToken.value = undefined; + tokenClaims.value = null; + } + + async function logout() { + cleanTokens(); + await router.push('/'); + } + + async function login(email, password) { + console.log('login called with email:', email); + if (!email || !password) { + throw new Error('Email and password are required'); } - function cleanTokens() { - console.log('cleanTokens called - clearing stored tokens'); - accessToken.value = undefined; - refreshToken.value = undefined; - tokenClaims.value = null; + try { + const response = await clientApi.post('api/users/login', { + email: email.trim(), + password: password, + }); + + if (!response.data?.accessToken || !response.data?.refreshToken) { + throw new Error('Invalid login response'); + } + + updateTokens(response.data); + console.log('login successful'); + return true; + } catch (error) { + console.error('Login failed:', error); + cleanTokens(); + throw error; + } + } + + async function loginWithGoogle(accessTokenParam) { + console.log('loginWithGoogle called'); + if (!accessTokenParam) { + throw new Error('Google access token is required'); } - async function logout() { - cleanTokens(); - await router.push('/'); + try { + const response = await clientApi.post('api/users/login-with-google', { + token: accessTokenParam, + }); + + if (!response.data?.accessToken || !response.data?.refreshToken) { + throw new Error('Invalid Google login response'); + } + + updateTokens(response.data); + console.log('Google login successful'); + return true; + } catch (error) { + console.error('Google login failed:', error); + cleanTokens(); + throw error; + } + } + + async function loginWithFacebook(authResponse) { + console.log('loginWithFacebook called'); + if (!authResponse?.accessToken) { + throw new Error('Facebook access token is required'); } - async function login(email, password) { - console.log('login called with email:', email); - if (!email || !password) { - throw new Error('Email and password are required'); - } + try { + const response = await clientApi.post('api/users/login-with-facebook', { + token: authResponse.accessToken, + }); + if (!response.data?.accessToken || !response.data?.refreshToken) { + throw new Error('Invalid Facebook login response'); + } + + updateTokens(response.data); + console.log('Facebook login successful'); + return true; + } catch (error) { + console.error('Facebook login failed:', error); + cleanTokens(); + throw error; + } + } + + async function refresh() { + console.log('refresh called'); + + if (!refreshToken.value) { + cleanTokens(); // Clear tokens first + throw new Error('No refresh token available'); + } + + if (isRefreshing.value && refreshPromise) { + console.log('Already refreshing, returning existing refreshPromise'); + return refreshPromise; + } + + try { + isRefreshing.value = true; + refreshPromise = (async () => { try { - const response = await clientApi.post('api/users/login', { - email: email.trim(), - password: password, + console.log('Sending refresh request...'); + + const response = await clientApi.post('api/users/refresh', { + refreshToken: refreshToken.value, + }); + + if (!response.data?.accessToken || !response.data?.refreshToken) { + throw new Error('Invalid refresh response'); + } + + updateTokens({ + accessToken: response.data.accessToken, + refreshToken: response.data.refreshToken, + }); + + console.log('Token refresh successful'); + return true; + } catch (error) { + console.error('Token refresh failed:', error); + cleanTokens(); + + const currentRoute = router.currentRoute.value; + const returnUrl = currentRoute.fullPath; + + // Handle navigation + router + .push({ + name: 'login', + query: { returnUrl }, + }) + .catch(navError => { + console.error('Navigation error after token refresh failure:', navError); }); - if (!response.data?.accessToken || !response.data?.refreshToken) { - throw new Error('Invalid login response'); - } - - updateTokens(response.data); - console.log('login successful'); - return true; - } catch (error) { - console.error('Login failed:', error); - cleanTokens(); - throw error; + throw error; // Re-throw to notify callers } + })(); + + return await refreshPromise; + } catch (error) { + throw error; + } finally { + // Ensure these are always reset, even if an error is thrown + isRefreshing.value = false; + refreshPromise = null; + } + } + + function getClaimsFromToken(token) { + if (!token) return null; + try { + return jwtDecode(token); + } catch (error) { + console.error('Failed to decode token:', error); + return null; + } + } + + function isTokenExpiringSoon(token) { + if (!token) { + console.log('No token provided, considered expiring soon'); + return true; } - async function loginWithGoogle(accessTokenParam) { - console.log('loginWithGoogle called'); - if (!accessTokenParam) { - throw new Error('Google access token is required'); - } - - try { - const response = await clientApi.post('api/users/login-with-google', { - token: accessTokenParam, - }); - - if (!response.data?.accessToken || !response.data?.refreshToken) { - throw new Error('Invalid Google login response'); - } - - updateTokens(response.data); - console.log('Google login successful'); - return true; - } catch (error) { - console.error('Google login failed:', error); - cleanTokens(); - throw error; - } + const claims = getClaimsFromToken(token); + if (!claims || !claims.exp) { + console.log('No valid claims found, considered expiring soon'); + return true; } - async function loginWithFacebook(authResponse) { - console.log('loginWithFacebook called'); - if (!authResponse?.accessToken) { - throw new Error('Facebook access token is required'); - } + const expirationTime = claims.exp * 1000; // Convert to milliseconds + const currentTime = Date.now(); + const fiveMinutesInMs = 2 * 60 * 1000; // 2 minutes for demonstration - try { - const response = await clientApi.post('api/users/login-with-facebook', { - token: authResponse.accessToken, - }); + // Calculate time remaining (can be negative if already expired) + const timeRemainingMs = expirationTime - currentTime; - if (!response.data?.accessToken || !response.data?.refreshToken) { - throw new Error('Invalid Facebook login response'); - } + // Token is expiring soon if less than 2 minutes remaining or already expired + const isExpiring = timeRemainingMs < fiveMinutesInMs; - updateTokens(response.data); - console.log('Facebook login successful'); - return true; - } catch (error) { - console.error('Facebook login failed:', error); - cleanTokens(); - throw error; - } + // Determine the sign for display purposes + const formattedTimeRemaining = + timeRemainingMs < 0 ? `-${formatDuration(Math.abs(timeRemainingMs))}` : formatDuration(timeRemainingMs); + + if (isExpiring) { + console.log(`Token expiration check; is token expired: ${isExpiring}`, { + expirationTime: new Date(expirationTime).toLocaleString(), + currentTime: new Date(currentTime).toLocaleString(), + timeRemaining: formattedTimeRemaining, + }); } - async function refresh() { - console.log('refresh called'); + return isExpiring; + } - if (!refreshToken.value) { - cleanTokens(); // Clear tokens first - throw new Error('No refresh token available'); - } - - if (isRefreshing.value && refreshPromise) { - console.log('Already refreshing, returning existing refreshPromise'); - return refreshPromise; - } - - try { - isRefreshing.value = true; - refreshPromise = (async () => { - try { - console.log('Sending refresh request...'); - - const response = await clientApi.post('api/users/refresh', { - refreshToken: refreshToken.value, - }); - - if (!response.data?.accessToken || !response.data?.refreshToken) { - throw new Error('Invalid refresh response'); - } - - updateTokens({ - accessToken: response.data.accessToken, - refreshToken: response.data.refreshToken, - }); - - console.log('Token refresh successful'); - return true; - } catch (error) { - console.error('Token refresh failed:', error); - cleanTokens(); - - const currentRoute = router.currentRoute.value; - const returnUrl = currentRoute.fullPath; - - // Handle navigation - router - .push({ - name: 'login', - query: { returnUrl }, - }) - .catch(navError => { - console.error('Navigation error after token refresh failure:', navError); - }); - - throw error; // Re-throw to notify callers - } - })(); - - return await refreshPromise; - } catch (error) { - throw error; - } finally { - // Ensure these are always reset, even if an error is thrown - isRefreshing.value = false; - refreshPromise = null; - } + async function changePassword(newPassword) { + console.log('changePassword called'); + if (!isAuthenticated.value) { + throw new Error('User must be authenticated to change password'); } - function getClaimsFromToken(token) { - if (!token) return null; - try { - return jwtDecode(token); - } catch (error) { - console.error('Failed to decode token:', error); - return null; - } + if (!newPassword) { + throw new Error('New password is required'); } - function isTokenExpiringSoon(token) { - if (!token) { - console.log('No token provided, considered expiring soon'); - return true; - } + try { + const response = await clientApi.post('api/users/set-password', { + newPassword, + }); - const claims = getClaimsFromToken(token); - if (!claims || !claims.exp) { - console.log('No valid claims found, considered expiring soon'); - return true; - } - - const expirationTime = claims.exp * 1000; // Convert to milliseconds - const currentTime = Date.now(); - const fiveMinutesInMs = 2 * 60 * 1000; // 2 minutes for demonstration - - // Calculate time remaining (can be negative if already expired) - const timeRemainingMs = expirationTime - currentTime; - - // Token is expiring soon if less than 2 minutes remaining or already expired - const isExpiring = timeRemainingMs < fiveMinutesInMs; - - // Determine the sign for display purposes - const formattedTimeRemaining = - timeRemainingMs < 0 ? `-${formatDuration(Math.abs(timeRemainingMs))}` : formatDuration(timeRemainingMs); - - if (isExpiring) { - console.log(`Token expiration check; is token expired: ${isExpiring}`, { - expirationTime: new Date(expirationTime).toLocaleString(), - currentTime: new Date(currentTime).toLocaleString(), - timeRemaining: formattedTimeRemaining, - }); - } - - return isExpiring; + console.log('Password changed successfully'); + return true; + } catch (error) { + console.error('Password change failed:', error); + throw error; } + } - async function changePassword(newPassword) { - console.log('changePassword called'); - if (!isAuthenticated.value) { - throw new Error('User must be authenticated to change password'); - } + function hasAnyRole(roles) { + return roles.some(role => userRoles.value.includes(role)); + } - if (!newPassword) { - throw new Error('New password is required'); - } - - try { - const response = await clientApi.post('api/users/set-password', { - newPassword, - }); - - console.log('Password changed successfully'); - return true; - } catch (error) { - console.error('Password change failed:', error); - throw error; - } - } - - function hasAnyRole(roles) { - return roles.some(role => userRoles.value.includes(role)); - } - - return { - accessToken, - refreshToken, - isAuthenticated, - userId, - userRoles, - persona, - hasAnyRole, - isManager, - isClient, - isProvider, - isRefreshing, - login, - loginWithGoogle, - loginWithFacebook, - logout, - refresh, - isTokenExpiringSoon, - changePassword, - }; + return { + accessToken, + refreshToken, + isAuthenticated, + userId, + userRoles, + persona, + hasAnyRole, + isManager, + isClient, + isProvider, + isRefreshing, + login, + loginWithGoogle, + loginWithFacebook, + logout, + refresh, + isTokenExpiringSoon, + changePassword, + }; }); diff --git a/frontend/src/features/projects/stores/projectsStore.js b/frontend/src/features/campaigns/stores/campaignsStore.js similarity index 67% rename from frontend/src/features/projects/stores/projectsStore.js rename to frontend/src/features/campaigns/stores/campaignsStore.js index cc557a8..c992bca 100644 --- a/frontend/src/features/projects/stores/projectsStore.js +++ b/frontend/src/features/campaigns/stores/campaignsStore.js @@ -4,19 +4,19 @@ import { useAuthStore } from '@/features/auth/stores/authStore.js'; import { useWorkspaceStore } from '@/features/workspaces/stores/workspaceStore.js'; import { useClient } from '@/plugins/api.js'; -export const useProjectsStore = defineStore('projects', () => { +export const useCampaignsStore = defineStore('campaigns', () => { const authStore = useAuthStore(); const workspaceStore = useWorkspaceStore(); const client = useClient(); - const projects = ref([]); + const campaigns = ref([]); const isLoading = ref(false); const isCreating = ref(false); const error = ref(null); - async function fetchProjects() { + async function fetchCampaigns() { if (!authStore.isAuthenticated || !workspaceStore.activeWorkspaceId) { - projects.value = []; + campaigns.value = []; error.value = null; return; } @@ -25,49 +25,49 @@ export const useProjectsStore = defineStore('projects', () => { error.value = null; try { - const response = await client.get('/api/projects', { + const response = await client.get('/api/campaigns', { params: { workspaceId: workspaceStore.activeWorkspaceId, }, }); - projects.value = response.data ?? []; + campaigns.value = response.data ?? []; } catch (fetchError) { - console.error('Failed to fetch projects:', fetchError); - projects.value = []; - error.value = 'Failed to load projects.'; + console.error('Failed to fetch campaigns:', fetchError); + campaigns.value = []; + error.value = 'Failed to load campaigns.'; } finally { isLoading.value = false; } } - async function createProject(payload) { + async function createCampaign(payload) { if (!authStore.isAuthenticated || !workspaceStore.activeWorkspaceId) { - throw new Error('You must be authenticated to create a project.'); + throw new Error('You must be authenticated to create a campaign.'); } if (isCreating.value) { - throw new Error('A project creation request is already in progress.'); + throw new Error('A campaign creation request is already in progress.'); } isCreating.value = true; error.value = null; try { - const response = await client.post('/api/projects', { + const response = await client.post('/api/campaigns', { ...payload, workspaceId: workspaceStore.activeWorkspaceId, }); if (response.data) { - projects.value = [...projects.value, response.data] + campaigns.value = [...campaigns.value, response.data] .sort((left, right) => left.name.localeCompare(right.name)); } return response.data; } catch (createError) { - console.error('Failed to create project:', createError); - error.value = 'Failed to create project.'; + console.error('Failed to create campaign:', createError); + error.value = 'Failed to create campaign.'; throw createError; } finally { isCreating.value = false; @@ -78,22 +78,22 @@ export const useProjectsStore = defineStore('projects', () => { () => [authStore.isAuthenticated, workspaceStore.activeWorkspaceId], async ([isAuthenticated, workspaceId]) => { if (!isAuthenticated || !workspaceId) { - projects.value = []; + campaigns.value = []; error.value = null; return; } - await fetchProjects(); + await fetchCampaigns(); }, { immediate: true } ); return { - projects, + campaigns, isLoading, isCreating, error, - fetchProjects, - createProject, + fetchCampaigns, + createCampaign, }; }); diff --git a/frontend/src/features/projects/views/ProjectDetailView.vue b/frontend/src/features/campaigns/views/CampaignDetailView.vue similarity index 84% rename from frontend/src/features/projects/views/ProjectDetailView.vue rename to frontend/src/features/campaigns/views/CampaignDetailView.vue index 9956050..8fbcf12 100644 --- a/frontend/src/features/projects/views/ProjectDetailView.vue +++ b/frontend/src/features/campaigns/views/CampaignDetailView.vue @@ -3,22 +3,22 @@ import { useRoute } from 'vue-router'; import { useAuthStore } from '@/features/auth/stores/authStore.js'; import { useWorkspaceStore } from '@/features/workspaces/stores/workspaceStore.js'; - import { useProjectsStore } from '@/features/projects/stores/projectsStore.js'; + import { useCampaignsStore } from '@/features/campaigns/stores/campaignsStore.js'; import { useContentItemsStore } from '@/features/content/stores/contentItemsStore.js'; const authStore = useAuthStore(); const route = useRoute(); const workspaceStore = useWorkspaceStore(); - const projectsStore = useProjectsStore(); + const campaignsStore = useCampaignsStore(); const contentItemsStore = useContentItemsStore(); - const project = computed(() => - projectsStore.projects.find(candidate => candidate.id === route.params.projectId) ?? null + const campaign = computed(() => + campaignsStore.campaigns.find(candidate => candidate.id === route.params.campaignId) ?? null ); const scopedItems = computed(() => contentItemsStore.items - .filter(item => item.projectId === route.params.projectId) + .filter(item => item.campaignId === route.params.campaignId) .sort((left, right) => { const leftDue = left.dueDate ? new Date(left.dueDate).getTime() : Number.MAX_SAFE_INTEGER; const rightDue = right.dueDate ? new Date(right.dueDate).getTime() : Number.MAX_SAFE_INTEGER; @@ -26,8 +26,8 @@ }) ); - function formatProjectDateRange(projectValue) { - if (!projectValue?.startDate || !projectValue?.endDate) { + function formatCampaignDateRange(campaignValue) { + if (!campaignValue?.startDate || !campaignValue?.endDate) { return 'No date range'; } @@ -35,14 +35,14 @@ month: 'short', day: 'numeric', year: 'numeric', - }).formatRange(new Date(projectValue.startDate), new Date(projectValue.endDate)); + }).formatRange(new Date(campaignValue.startDate), new Date(campaignValue.endDate)); } @@ -267,9 +267,9 @@ .header p, .panel-header span, - .project-row span, - .project-meta span, - .project-meta em { + .campaign-row span, + .campaign-meta span, + .campaign-meta em { @apply text-sm leading-6 not-italic; color: #526178; } @@ -296,7 +296,7 @@ } .create-panel, - .project-row { + .campaign-row { @apply rounded-[1.5rem] border; background: rgba(255, 255, 255, 0.9); border-color: rgba(23, 32, 51, 0.08); @@ -311,7 +311,7 @@ } .panel-header strong, - .project-row strong { + .campaign-row strong { color: #172033; } @@ -347,19 +347,19 @@ @apply flex justify-end gap-3; } - .project-stack { + .campaign-stack { @apply flex flex-col gap-4; } - .project-row { + .campaign-row { @apply flex flex-col justify-between gap-4 p-5 no-underline lg:flex-row lg:items-center; } - .project-row strong { + .campaign-row strong { @apply block text-xl font-black; } - .project-meta { + .campaign-meta { @apply flex flex-col items-start gap-1 lg:items-end; } diff --git a/frontend/src/features/clients/views/ClientDetailView.vue b/frontend/src/features/clients/views/ClientDetailView.vue index ceb74e0..c62ff1e 100644 --- a/frontend/src/features/clients/views/ClientDetailView.vue +++ b/frontend/src/features/clients/views/ClientDetailView.vue @@ -5,13 +5,13 @@ import ImageCropperDialog from '@/components/ImageCropperDialog.vue'; import { useAuthStore } from '@/features/auth/stores/authStore.js'; import { useClientsStore } from '@/features/clients/stores/clientsStore.js'; - import { useProjectsStore } from '@/features/projects/stores/projectsStore.js'; + import { useCampaignsStore } from '@/features/campaigns/stores/campaignsStore.js'; import { useContentItemsStore } from '@/features/content/stores/contentItemsStore.js'; const authStore = useAuthStore(); const route = useRoute(); const clientsStore = useClientsStore(); - const projectsStore = useProjectsStore(); + const campaignsStore = useCampaignsStore(); const contentItemsStore = useContentItemsStore(); const isEditFormVisible = ref(false); const isPortraitDialogOpen = ref(false); @@ -48,9 +48,9 @@ clientsStore.clients.find(candidate => candidate.id === route.params.clientId) ?? null ); - const scopedProjects = computed(() => - projectsStore.projects - .filter(project => project.clientId === route.params.clientId) + const scopedCampaigns = computed(() => + campaignsStore.campaigns + .filter(campaign => campaign.clientId === route.params.clientId) .sort((left, right) => { const leftDue = left.endDate ? new Date(left.endDate).getTime() : Number.MAX_SAFE_INTEGER; const rightDue = right.endDate ? new Date(right.endDate).getTime() : Number.MAX_SAFE_INTEGER; @@ -58,26 +58,26 @@ }) ); - const currentProjects = computed(() => - scopedProjects.value.filter(project => project.status !== 'Completed' && project.status !== 'Archived') + const currentCampaigns = computed(() => + scopedCampaigns.value.filter(campaign => campaign.status !== 'Completed' && campaign.status !== 'Archived') ); - const pastProjects = computed(() => - scopedProjects.value.filter(project => project.status === 'Completed' || project.status === 'Archived') + const pastCampaigns = computed(() => + scopedCampaigns.value.filter(campaign => campaign.status === 'Completed' || campaign.status === 'Archived') ); - const itemCountByProjectId = computed(() => { + const itemCountByCampaignId = computed(() => { const counts = new Map(); for (const item of contentItemsStore.items.filter(candidate => candidate.clientId === route.params.clientId)) { - counts.set(item.projectId, (counts.get(item.projectId) ?? 0) + 1); + counts.set(item.campaignId, (counts.get(item.campaignId) ?? 0) + 1); } return counts; }); - function formatProjectDateRange(project) { - if (!project?.startDate || !project?.endDate) { + function formatCampaignDateRange(campaign) { + if (!campaign?.startDate || !campaign?.endDate) { return 'No date range'; } @@ -85,7 +85,7 @@ month: 'short', day: 'numeric', year: 'numeric', - }).formatRange(new Date(project.startDate), new Date(project.endDate)); + }).formatRange(new Date(campaign.startDate), new Date(campaign.endDate)); } function syncForm() { @@ -188,18 +188,18 @@
{{ client.status }}
-

The client area scopes projects and content so review stays inside one account.

+

The client area scopes campaigns and content so review stays inside one account.

Current campaigns - {{ currentProjects.length }} + {{ currentCampaigns.length }}
Past campaigns - {{ pastProjects.length }} + {{ pastCampaigns.length }}
Total content items @@ -420,26 +420,26 @@
Current campaigns - {{ currentProjects.length }} active + {{ currentCampaigns.length }} active
- {{ project.name }} - {{ project.status }} + {{ campaign.name }} + {{ campaign.status }}
-
- {{ itemCountByProjectId.get(project.id) ?? 0 }} content items - {{ formatProjectDateRange(project) }} +
+ {{ itemCountByCampaignId.get(campaign.id) ?? 0 }} content items + {{ formatCampaignDateRange(campaign) }}
@@ -454,26 +454,26 @@
Past campaigns - {{ pastProjects.length }} archived or completed + {{ pastCampaigns.length }} archived or completed
- {{ project.name }} - {{ project.status }} + {{ campaign.name }} + {{ campaign.status }}
-
- {{ itemCountByProjectId.get(project.id) ?? 0 }} content items - {{ formatProjectDateRange(project) }} +
+ {{ itemCountByCampaignId.get(campaign.id) ?? 0 }} content items + {{ formatCampaignDateRange(campaign) }}
@@ -489,7 +489,7 @@ .hero, .stat-card, - .project-card { + .campaign-card { @apply rounded-[1.5rem] border; background: rgba(255, 255, 255, 0.9); border-color: rgba(23, 32, 51, 0.08); @@ -501,7 +501,7 @@ .hero-main h1, .stat-card strong, - .project-card strong, + .campaign-card strong, .contact-card strong { color: #172033; } @@ -513,9 +513,9 @@ .hero-main p, .breadcrumb, .stat-card span, - .project-card span, - .project-card small, - .project-card em, + .campaign-card span, + .campaign-card small, + .campaign-card em, .section-header span { @apply text-sm leading-6 not-italic; color: #526178; @@ -675,27 +675,27 @@ color: #172033; } - .project-list { + .campaign-list { @apply grid gap-4 md:grid-cols-2; } - .project-card { + .campaign-card { @apply flex flex-col gap-4 p-5 no-underline transition; } - .project-card:hover { + .campaign-card:hover { transform: translateY(-2px); } - .project-card.muted { + .campaign-card.muted { background: rgba(255, 250, 242, 0.88); } - .project-card span { + .campaign-card span { @apply uppercase tracking-[0.16em]; } - .project-meta { + .campaign-meta { @apply flex items-center justify-between gap-3; } diff --git a/frontend/src/features/content/stores/contentItemsStore.js b/frontend/src/features/content/stores/contentItemsStore.js index 3f6e764..d94c6a5 100644 --- a/frontend/src/features/content/stores/contentItemsStore.js +++ b/frontend/src/features/content/stores/contentItemsStore.js @@ -34,7 +34,7 @@ export const useContentItemsStore = defineStore('content-items', () => { params: { workspaceId: workspaceStore.activeWorkspaceId, clientId: filters.clientId, - projectId: filters.projectId, + campaignId: filters.campaignId, }, }); diff --git a/frontend/src/features/content/views/ContentItemDetailView.vue b/frontend/src/features/content/views/ContentItemDetailView.vue index 0560d6c..284e29f 100644 --- a/frontend/src/features/content/views/ContentItemDetailView.vue +++ b/frontend/src/features/content/views/ContentItemDetailView.vue @@ -7,13 +7,13 @@ import { useClientsStore } from '@/features/clients/stores/clientsStore.js'; import { useContentItemDetailStore } from '@/features/content/stores/contentItemDetailStore.js'; import { useContentItemsStore } from '@/features/content/stores/contentItemsStore.js'; - import { useProjectsStore } from '@/features/projects/stores/projectsStore.js'; + import { useCampaignsStore } from '@/features/campaigns/stores/campaignsStore.js'; import { useWorkspaceStore } from '@/features/workspaces/stores/workspaceStore.js'; const route = useRoute(); const router = useRouter(); const workspaceStore = useWorkspaceStore(); - const projectsStore = useProjectsStore(); + const campaignsStore = useCampaignsStore(); const clientsStore = useClientsStore(); const channelsStore = useChannelsStore(); const contentItemsStore = useContentItemsStore(); @@ -25,7 +25,7 @@ const form = reactive({ title: '', - projectId: '', + campaignId: '', dueDate: '', body: '', hashtags: '', @@ -60,7 +60,7 @@ const isCreateMode = computed(() => route.name === 'content-item-create'); const contentItemId = computed(() => isCreateMode.value ? null : route.params.id); const item = computed(() => detailStore.item); - const availableProjects = computed(() => projectsStore.projects); + const availableCampaigns = computed(() => campaignsStore.campaigns); const availableChannels = computed(() => channelsStore.channels); const groupedChannels = computed(() => { const groups = new Map(); @@ -84,10 +84,10 @@ .join(', ') ); const operationalClient = computed(() => clientsStore.operationalClient); - const projectNameById = computed(() => - new Map(projectsStore.projects.map(project => [project.id, project.name])) + const campaignNameById = computed(() => + new Map(campaignsStore.campaigns.map(campaign => [campaign.id, campaign.name])) ); - const editorKey = computed(() => isCreateMode.value ? `new:${route.query.projectId ?? 'default'}` : String(route.params.id)); + const editorKey = computed(() => isCreateMode.value ? `new:${route.query.campaignId ?? 'default'}` : String(route.params.id)); const isMultiLevelApproval = computed(() => workspaceStore.activeWorkspace?.approvalMode === 'Multi-level'); function blankPlacement(channel = null) { @@ -181,7 +181,7 @@ function serializeDraft() { return JSON.parse(JSON.stringify({ title: form.title, - projectId: form.projectId, + campaignId: form.campaignId, dueDate: form.dueDate, body: form.body, hashtags: form.hashtags, @@ -192,7 +192,7 @@ function restoreDraft(draft) { form.title = draft.title ?? ''; - form.projectId = draft.projectId ?? availableProjects.value[0]?.id ?? ''; + form.campaignId = draft.campaignId ?? availableCampaigns.value[0]?.id ?? ''; form.dueDate = draft.dueDate ?? ''; form.body = draft.body ?? ''; form.hashtags = draft.hashtags ?? ''; @@ -215,7 +215,7 @@ } function buildDraftFromItem() { - const projectId = item.value?.projectId ?? ''; + const campaignId = item.value?.campaignId ?? ''; const placements = parseTargets(item.value?.publicationTargets).map(target => { const channel = availableChannels.value.find(candidate => candidate.name.toLowerCase() === target.toLowerCase()); @@ -233,7 +233,7 @@ restoreDraft({ title: item.value?.title ?? '', - projectId, + campaignId, dueDate: item.value?.dueDate ? new Date(item.value.dueDate).toISOString().slice(0, 10) : '', body: item.value?.publicationMessage ?? '', hashtags: item.value?.hashtags ?? '', @@ -243,13 +243,13 @@ } function buildDraftForNew() { - const projectIdFromRoute = typeof route.query.projectId === 'string' ? route.query.projectId : ''; + const campaignIdFromRoute = typeof route.query.campaignId === 'string' ? route.query.campaignId : ''; restoreDraft({ title: '', - projectId: availableProjects.value.some(project => project.id === projectIdFromRoute) - ? projectIdFromRoute - : availableProjects.value[0]?.id ?? '', + campaignId: availableCampaigns.value.some(campaign => campaign.id === campaignIdFromRoute) + ? campaignIdFromRoute + : availableCampaigns.value[0]?.id ?? '', dueDate: '', body: '', hashtags: '', @@ -302,7 +302,7 @@ async function saveContent() { saveError.message = ''; - if (!form.title.trim() || !form.projectId || !form.placements.length) { + if (!form.title.trim() || !form.campaignId || !form.placements.length) { saveError.message = 'Title, campaign, and at least one channel are required.'; return; } @@ -314,7 +314,7 @@ const payload = { title: form.title.trim(), - projectId: form.projectId, + campaignId: form.campaignId, publicationMessage: form.body.trim(), publicationTargets: placementSummary.value, hashtags: form.hashtags.trim(), @@ -408,8 +408,8 @@ () => [ isCreateMode.value, route.params.id, - route.query.projectId, - availableProjects.value.length, + route.query.campaignId, + availableCampaigns.value.length, availableChannels.value.length, ], async () => { @@ -421,7 +421,7 @@ watch( () => [ form.title, - form.projectId, + form.campaignId, form.dueDate, form.body, form.hashtags, @@ -467,7 +467,7 @@
{{ isCreateMode ? 'New content' : 'Content item' }}

{{ form.title || 'Untitled content' }}

- {{ projectNameById.get(form.projectId) || 'Choose a campaign' }} + {{ campaignNameById.get(form.campaignId) || 'Choose a campaign' }} @@ -664,7 +664,7 @@ diff --git a/frontend/src/features/feedback/components/FeedbackSubmissionDialog.vue b/frontend/src/features/feedback/components/FeedbackSubmissionDialog.vue index e2fc9ec..98d323c 100644 --- a/frontend/src/features/feedback/components/FeedbackSubmissionDialog.vue +++ b/frontend/src/features/feedback/components/FeedbackSubmissionDialog.vue @@ -8,7 +8,7 @@ import { useContentItemDetailStore } from '@/features/content/stores/contentItemDetailStore.js'; import { useContentItemsStore } from '@/features/content/stores/contentItemsStore.js'; import { useFeedbackSubmissionStore } from '@/features/feedback/stores/feedbackSubmissionStore.js'; - import { useProjectsStore } from '@/features/projects/stores/projectsStore.js'; + import { useCampaignsStore } from '@/features/campaigns/stores/campaignsStore.js'; import { useWorkspaceStore } from '@/features/workspaces/stores/workspaceStore.js'; import { mdiArrowTopRight, @@ -33,7 +33,7 @@ const contentItemsStore = useContentItemsStore(); const contentItemDetailStore = useContentItemDetailStore(); const feedbackStore = useFeedbackSubmissionStore(); - const projectsStore = useProjectsStore(); + const campaignsStore = useCampaignsStore(); const workspaceStore = useWorkspaceStore(); const form = reactive({ @@ -83,16 +83,16 @@ ? contentItemDetailStore.item : contentItemsStore.items.find(item => item.id === routeId) ?? null; }); - const currentProject = computed(() => { - const projectId = route.params.projectId ?? currentContentItem.value?.projectId; - if (!projectId) { + const currentCampaign = computed(() => { + const campaignId = route.params.campaignId ?? currentContentItem.value?.campaignId; + if (!campaignId) { return null; } - return projectsStore.projects.find(project => project.id === projectId) ?? null; + return campaignsStore.campaigns.find(campaign => campaign.id === campaignId) ?? null; }); const currentClient = computed(() => { - const clientId = route.query.clientId ?? currentProject.value?.clientId ?? currentContentItem.value?.clientId; + const clientId = route.query.clientId ?? currentCampaign.value?.clientId ?? currentContentItem.value?.clientId; if (!clientId) { return clientsStore.operationalClient ?? null; } @@ -447,8 +447,8 @@ workspaceName: workspaceStore.activeWorkspace?.name ?? null, clientId: currentClient.value?.id ?? null, clientName: currentClient.value?.name ?? null, - projectId: currentProject.value?.id ?? null, - projectName: currentProject.value?.name ?? null, + campaignId: currentCampaign.value?.id ?? null, + campaignName: currentCampaign.value?.name ?? null, contentItemId: currentContentItem.value?.id ?? null, contentItemTitle: currentContentItem.value?.title ?? null, }; diff --git a/frontend/src/features/feedback/stores/developerFeedbackStore.js b/frontend/src/features/feedback/stores/developerFeedbackStore.js index 6febc7d..e5b9ff6 100644 --- a/frontend/src/features/feedback/stores/developerFeedbackStore.js +++ b/frontend/src/features/feedback/stores/developerFeedbackStore.js @@ -82,7 +82,7 @@ export const useDeveloperFeedbackStore = defineStore('developer-feedback', () => report.metadata?.submittedPath, report.context?.workspaceName, report.context?.clientName, - report.context?.projectName, + report.context?.campaignName, report.context?.contentItemTitle, ...(report.tags ?? []), ] diff --git a/frontend/src/features/feedback/views/DeveloperFeedbackDetailView.vue b/frontend/src/features/feedback/views/DeveloperFeedbackDetailView.vue index 7693170..3806298 100644 --- a/frontend/src/features/feedback/views/DeveloperFeedbackDetailView.vue +++ b/frontend/src/features/feedback/views/DeveloperFeedbackDetailView.vue @@ -54,7 +54,7 @@ return [ [t('feedback.review.detail.context.workspace'), context?.workspaceName ?? context?.workspaceId], [t('feedback.review.detail.context.client'), context?.clientName ?? context?.clientId], - [t('feedback.review.detail.context.project'), context?.projectName ?? context?.projectId], + [t('feedback.review.detail.context.campaign'), context?.campaignName ?? context?.campaignId], [t('feedback.review.detail.context.contentItem'), context?.contentItemTitle ?? context?.contentItemId], ]; }); diff --git a/frontend/src/features/feedback/views/DeveloperFeedbackListView.vue b/frontend/src/features/feedback/views/DeveloperFeedbackListView.vue index 5be8fa2..15b9b85 100644 --- a/frontend/src/features/feedback/views/DeveloperFeedbackListView.vue +++ b/frontend/src/features/feedback/views/DeveloperFeedbackListView.vue @@ -49,7 +49,7 @@ return [ report.context?.workspaceName, report.context?.clientName, - report.context?.projectName, + report.context?.campaignName, report.context?.contentItemTitle, ] .filter(Boolean) diff --git a/frontend/src/features/notifications/notificationRoutes.js b/frontend/src/features/notifications/notificationRoutes.js index f50ef5a..7ad651b 100644 --- a/frontend/src/features/notifications/notificationRoutes.js +++ b/frontend/src/features/notifications/notificationRoutes.js @@ -6,7 +6,7 @@ export function getNotificationRoute(notification, authStore) { if (isFeedbackNotification(notification)) { return { - name: authStore.hasAnyRole(['Developer']) ? 'developer-feedback-detail' : 'my-feedback-detail', + name: authStore.hasAnyRole(['developer']) ? 'developer-feedback-detail' : 'my-feedback-detail', params: { id: notification.entityId }, }; } diff --git a/frontend/src/features/reviews/stores/reviewQueueStore.js b/frontend/src/features/reviews/stores/reviewQueueStore.js index 0e6267a..ee19138 100644 --- a/frontend/src/features/reviews/stores/reviewQueueStore.js +++ b/frontend/src/features/reviews/stores/reviewQueueStore.js @@ -1,7 +1,7 @@ import { computed } from 'vue'; import { defineStore } from 'pinia'; import { useContentItemsStore } from '@/features/content/stores/contentItemsStore.js'; -import { useProjectsStore } from '@/features/projects/stores/projectsStore.js'; +import { useCampaignsStore } from '@/features/campaigns/stores/campaignsStore.js'; const stageByStatus = { Draft: 'Draft', @@ -14,18 +14,18 @@ const stageByStatus = { export const useReviewQueueStore = defineStore('review-queue', () => { const contentItemsStore = useContentItemsStore(); - const projectsStore = useProjectsStore(); + const campaignsStore = useCampaignsStore(); const items = computed(() => contentItemsStore.items .filter(item => item.status === 'In approval') .map(item => { - const project = projectsStore.projects.find(candidate => candidate.id === item.projectId); + const campaign = campaignsStore.campaigns.find(candidate => candidate.id === item.campaignId); return { id: item.id, title: item.title, - projectName: project?.name ?? 'Unknown campaign', + campaignName: campaign?.name ?? 'Unknown campaign', stage: stageByStatus[item.status] ?? item.status, status: item.status, dueLabel: item.dueDate ? `Due ${new Date(item.dueDate).toLocaleDateString()}` : 'No due date', diff --git a/frontend/src/features/reviews/views/ReviewQueueView.vue b/frontend/src/features/reviews/views/ReviewQueueView.vue index 8dc6878..1394bcb 100644 --- a/frontend/src/features/reviews/views/ReviewQueueView.vue +++ b/frontend/src/features/reviews/views/ReviewQueueView.vue @@ -24,7 +24,7 @@ >

{{ item.title }} - {{ item.projectName }} · {{ item.stage }} + {{ item.campaignName }} · {{ item.stage }}
{{ item.status }} diff --git a/frontend/src/features/user-profile/stores/userProfileStore.js b/frontend/src/features/user-profile/stores/userProfileStore.js index ff33688..a9c8118 100644 --- a/frontend/src/features/user-profile/stores/userProfileStore.js +++ b/frontend/src/features/user-profile/stores/userProfileStore.js @@ -60,7 +60,7 @@ export const useUserProfileStore = defineStore( const persona = computed(() => value.value?.persona ?? null) const authorizedWorkspaceIds = computed(() => value.value?.authorizedWorkspaceIds ?? []) const authorizedClientIds = computed(() => value.value?.authorizedClientIds ?? []) - const authorizedProjectIds = computed(() => value.value?.authorizedProjectIds ?? []) + const authorizedCampaignIds = computed(() => value.value?.authorizedCampaignIds ?? []) async function fetchCurrentUserProfile() { try { @@ -214,7 +214,7 @@ export const useUserProfileStore = defineStore( persona, authorizedWorkspaceIds, authorizedClientIds, - authorizedProjectIds, + authorizedCampaignIds, changeFullname, changeAlias, changeBirthday, diff --git a/frontend/src/features/workspaces/views/DashboardView.vue b/frontend/src/features/workspaces/views/DashboardView.vue index e03794d..05f177b 100644 --- a/frontend/src/features/workspaces/views/DashboardView.vue +++ b/frontend/src/features/workspaces/views/DashboardView.vue @@ -3,12 +3,12 @@ import { useI18n } from 'vue-i18n'; import { mdiChevronLeft, mdiChevronRight } from '@mdi/js'; import { useWorkspaceStore } from '@/features/workspaces/stores/workspaceStore.js'; - import { useProjectsStore } from '@/features/projects/stores/projectsStore.js'; + import { useCampaignsStore } from '@/features/campaigns/stores/campaignsStore.js'; import { useContentItemsStore } from '@/features/content/stores/contentItemsStore.js'; const { t, locale } = useI18n(); const workspaceStore = useWorkspaceStore(); - const projectsStore = useProjectsStore(); + const campaignsStore = useCampaignsStore(); const contentItemsStore = useContentItemsStore(); const today = startOfDay(new Date()); @@ -24,28 +24,28 @@ Published: { tone: 'published', readiness: 'published' }, }; - const contentItemsByProjectId = computed(() => { + const contentItemsByCampaignId = computed(() => { const grouped = new Map(); for (const item of contentItemsStore.items) { - const existing = grouped.get(item.projectId) ?? []; + const existing = grouped.get(item.campaignId) ?? []; existing.push(item); - grouped.set(item.projectId, existing); + grouped.set(item.campaignId, existing); } return grouped; }); const calendarEntries = computed(() => { - const projectEntries = projectsStore.projects - .filter(project => project.endDate || project.startDate) - .map(project => buildProjectEntry(project)); + const campaignEntries = campaignsStore.campaigns + .filter(campaign => campaign.endDate || campaign.startDate) + .map(campaign => buildCampaignEntry(campaign)); const contentEntries = contentItemsStore.items .filter(item => item.dueDate) .map(item => buildContentEntry(item)); - return [...projectEntries, ...contentEntries].sort(sortByDate); + return [...campaignEntries, ...contentEntries].sort(sortByDate); }); const entriesByDay = computed(() => { @@ -119,11 +119,11 @@ }); const isLoading = computed(() => - workspaceStore.isLoading || projectsStore.isLoading || contentItemsStore.isLoading + workspaceStore.isLoading || campaignsStore.isLoading || contentItemsStore.isLoading ); const pageError = computed(() => - workspaceStore.error || projectsStore.error || contentItemsStore.error + workspaceStore.error || campaignsStore.error || contentItemsStore.error ); function buildDay(date, isOutsideMonth) { @@ -140,13 +140,13 @@ function buildContentEntry(item) { const statusMeta = contentStatusMeta[item.status] ?? { tone: 'production', readiness: 'building' }; - const project = projectsStore.projects.find(candidate => candidate.id === item.projectId); + const campaign = campaignsStore.campaigns.find(candidate => candidate.id === item.campaignId); return { id: item.id, type: 'content', title: item.title, - subtitle: project?.name ?? t('dashboard.labels.unassignedProject'), + subtitle: campaign?.name ?? t('dashboard.labels.unassignedCampaign'), scheduledAt: new Date(item.dueDate), dayKey: dateKey(item.dueDate), timeLabel: formatHour(item.dueDate), @@ -155,22 +155,22 @@ }; } - function buildProjectEntry(project) { - const projectItems = contentItemsByProjectId.value.get(project.id) ?? []; - const approvedCount = projectItems.filter(item => ['Approved', 'Scheduled', 'Published'].includes(item.status)).length; + function buildCampaignEntry(campaign) { + const campaignItems = contentItemsByCampaignId.value.get(campaign.id) ?? []; + const approvedCount = campaignItems.filter(item => ['Approved', 'Scheduled', 'Published'].includes(item.status)).length; return { - id: project.id, - type: 'project', - title: project.name, - subtitle: projectItems.length - ? t('dashboard.projectProgress', { scheduled: projectItems.length, approved: approvedCount }) + id: campaign.id, + type: 'campaign', + title: campaign.name, + subtitle: campaignItems.length + ? t('dashboard.campaignProgress', { scheduled: campaignItems.length, approved: approvedCount }) : t('dashboard.readiness.missing'), - scheduledAt: new Date(project.endDate ?? project.startDate), - dayKey: dateKey(project.endDate ?? project.startDate), + scheduledAt: new Date(campaign.endDate ?? campaign.startDate), + dayKey: dateKey(campaign.endDate ?? campaign.startDate), timeLabel: t('dashboard.campaignDeadline'), - tone: projectItems.length ? 'project' : 'risk', - route: { name: 'campaign-detail', params: { projectId: project.id } }, + tone: campaignItems.length ? 'campaign' : 'risk', + route: { name: 'campaign-detail', params: { campaignId: campaign.id } }, }; } @@ -553,7 +553,7 @@ border-color: rgba(220, 38, 38, 0.16); } - .calendar-entry.project { + .calendar-entry.campaign { background: #f8fafc; border-color: rgba(71, 85, 105, 0.18); border-style: dashed; diff --git a/frontend/src/features/workspaces/views/OverviewView.vue b/frontend/src/features/workspaces/views/OverviewView.vue index 1d95494..62037a5 100644 --- a/frontend/src/features/workspaces/views/OverviewView.vue +++ b/frontend/src/features/workspaces/views/OverviewView.vue @@ -12,7 +12,7 @@ const isLoading = ref(false); const error = ref(null); - const projects = ref([]); + const campaigns = ref([]); const contentItems = ref([]); const notifications = ref([]); @@ -22,7 +22,7 @@ const workspaceStats = computed(() => workspaceStore.workspaces.map(workspace => { - const workspaceProjects = projects.value.filter(project => project.workspaceId === workspace.id); + const workspaceCampaigns = campaigns.value.filter(campaign => campaign.workspaceId === workspace.id); const workspaceContent = contentItems.value.filter(item => item.workspaceId === workspace.id); const upcomingCount = workspaceContent.filter(item => { if (!item.dueDate) { @@ -38,7 +38,7 @@ id: workspace.id, name: workspace.name, timeZone: workspace.timeZone, - projectCount: workspaceProjects.length, + campaignCount: workspaceCampaigns.length, contentCount: workspaceContent.length, upcomingCount, blockingCount, @@ -94,14 +94,14 @@ const overviewStats = computed(() => [ { label: t('overview.stats.workspaces'), value: workspaceStore.workspaces.length }, - { label: t('overview.stats.projects'), value: projects.value.length }, + { label: t('overview.stats.campaigns'), value: campaigns.value.length }, { label: t('overview.stats.upcoming'), value: upcomingEvents.value.length }, { label: t('overview.stats.blockers'), value: crossWorkspaceRisks.value.length }, ]); async function loadOverview() { if (!authStore.isAuthenticated) { - projects.value = []; + campaigns.value = []; contentItems.value = []; notifications.value = []; return; @@ -111,19 +111,19 @@ error.value = null; try { - const [projectsResponse, contentItemsResponse, notificationsResponse] = await Promise.all([ - client.get('/api/projects'), + const [campaignsResponse, contentItemsResponse, notificationsResponse] = await Promise.all([ + client.get('/api/campaigns'), client.get('/api/content-items'), client.get('/api/notifications'), ]); - projects.value = projectsResponse.data ?? []; + campaigns.value = campaignsResponse.data ?? []; contentItems.value = contentItemsResponse.data ?? []; notifications.value = notificationsResponse.data ?? []; } catch (loadError) { console.error('Failed to load cross-workspace overview:', loadError); error.value = 'Failed to load overview data.'; - projects.value = []; + campaigns.value = []; contentItems.value = []; notifications.value = []; } finally { @@ -159,7 +159,7 @@ if (isAuthenticated) { await loadOverview(); } else { - projects.value = []; + campaigns.value = []; contentItems.value = []; notifications.value = []; } @@ -236,7 +236,7 @@ {{ workspace.timeZone }}
- {{ workspace.projectCount }} {{ t('overview.labels.projects') }} + {{ workspace.campaignCount }} {{ t('overview.labels.campaigns') }} {{ workspace.upcomingCount }} {{ t('overview.labels.upcoming') }} {{ workspace.blockingCount }} {{ t('overview.labels.blocked') }}
diff --git a/frontend/src/layouts/main/AppBar.vue b/frontend/src/layouts/main/AppBar.vue index 351195b..4856f57 100644 --- a/frontend/src/layouts/main/AppBar.vue +++ b/frontend/src/layouts/main/AppBar.vue @@ -44,7 +44,7 @@ case 'campaigns': return [{ key: 'create-campaign', - label: t('projects.newProject'), + label: t('campaigns.newCampaign'), icon: mdiPlus, route: { name: 'campaigns', query: { create: 'true' } }, }]; diff --git a/frontend/src/layouts/main/AppSidebar.vue b/frontend/src/layouts/main/AppSidebar.vue index 774680a..01431b0 100644 --- a/frontend/src/layouts/main/AppSidebar.vue +++ b/frontend/src/layouts/main/AppSidebar.vue @@ -9,7 +9,7 @@ import { useNotificationsStore } from '@/features/notifications/stores/notificationsStore.js'; import { getNotificationRoute } from '@/features/notifications/notificationRoutes.js'; import { useContentItemsStore } from '@/features/content/stores/contentItemsStore.js'; - import { useProjectsStore } from '@/features/projects/stores/projectsStore.js'; + import { useCampaignsStore } from '@/features/campaigns/stores/campaignsStore.js'; import { useUserProfileStore } from '@/features/user-profile/stores/userProfileStore.js'; import { mdiBellOutline, @@ -41,7 +41,7 @@ const contentItemsStore = useContentItemsStore(); const languageStore = useLanguageStore(); const notificationsStore = useNotificationsStore(); - const projectsStore = useProjectsStore(); + const campaignsStore = useCampaignsStore(); const userProfileStore = useUserProfileStore(); const isUserMenuOpen = ref(false); const isNotificationsOpen = ref(false); @@ -57,7 +57,7 @@ { to: '/app/workspace', labelKey: 'nav.workspacePlan', icon: mdiCalendarMonthOutline }, { to: '/app/media-library', labelKey: 'nav.mediaLibrary', icon: mdiImageMultipleOutline }, { to: '/app/my-feedback', labelKey: 'nav.myFeedback', icon: mdiBugOutline }, - { to: '/app/feedback', labelKey: 'nav.feedbackReview', icon: mdiBugOutline, roles: ['Developer'] }, + { to: '/app/feedback', labelKey: 'nav.feedbackReview', icon: mdiBugOutline, roles: ['developer'] }, { to: '/app/workspace-settings', labelKey: 'nav.settings', icon: mdiCogOutline }, ]; const visiblePrimaryLinks = computed(() => @@ -66,23 +66,23 @@ const openSections = ref({ channels: false, - projects: false, + campaigns: false, }); const normalizedSearchQuery = computed(() => searchQuery.value.trim().toLowerCase()); - const projectResults = computed(() => { + const campaignResults = computed(() => { if (!normalizedSearchQuery.value) { return []; } - return projectsStore.projects - .filter(project => project.name.toLowerCase().includes(normalizedSearchQuery.value)) + return campaignsStore.campaigns + .filter(campaign => campaign.name.toLowerCase().includes(normalizedSearchQuery.value)) .slice(0, 5) - .map(project => ({ - id: project.id, - label: project.name, + .map(campaign => ({ + id: campaign.id, + label: campaign.name, description: 'Campaign', - route: { name: 'campaign-detail', params: { projectId: project.id } }, + route: { name: 'campaign-detail', params: { campaignId: campaign.id } }, })); }); const contentResults = computed(() => { @@ -105,7 +105,7 @@ })); }); const hasSearchResults = computed(() => - projectResults.value.length > 0 || contentResults.value.length > 0 + campaignResults.value.length > 0 || contentResults.value.length > 0 ); const isSearchOpen = computed(() => isSearchFocused.value && normalizedSearchQuery.value.length > 0); @@ -209,7 +209,7 @@ } if (path.startsWith('/app/campaigns')) { - openSections.value.projects = true; + openSections.value.campaigns = true; } }, { immediate: true } @@ -270,13 +270,13 @@ class="sidebar-floating-panel" >
diff --git a/frontend/src/locales/en.json b/frontend/src/locales/en.json index bdf14a1..bf93ed9 100644 --- a/frontend/src/locales/en.json +++ b/frontend/src/locales/en.json @@ -74,7 +74,7 @@ "myFeedback": "My Feedback", "feedbackReview": "Feedback Review", "channels": "Channels", - "projects": "Campaigns", + "campaigns": "Campaigns", "reviewQueue": "Review Queue", "content": "Content", "profile": "Profile", @@ -210,7 +210,7 @@ "title": "Context", "workspace": "Workspace", "client": "Client", - "project": "Campaign", + "campaign": "Campaign", "contentItem": "Content item" }, "activity": { @@ -253,11 +253,11 @@ "sidebar": { "allClients": "All clients", "allChannels": "All channels", - "allProjects": "All campaigns", + "allCampaigns": "All campaigns", "allReviewItems": "Full review queue", "noClients": "No clients yet.", "noChannels": "No channels yet.", - "noProjects": "No campaigns yet.", + "noCampaigns": "No campaigns yet.", "noReviewItems": "No review items right now." }, "settings": { @@ -281,12 +281,12 @@ "deliveryRisks": "What can slip", "overdueItems": "Overdue items", "approvalBlockers": "Awaiting approval or revisions", - "unscheduledProjects": "Campaigns without scheduled content", + "unscheduledCampaigns": "Campaigns without scheduled content", "reviewQueueSnapshot": "Review queue snapshot", "emptyUpcoming": "No upcoming scheduled content.", "emptyOverdue": "Nothing overdue right now.", "emptyApproval": "No approval blockers at the moment.", - "emptyProjects": "Every campaign has at least one scheduled content item.", + "emptyCampaigns": "Every campaign has at least one scheduled content item.", "emptyReviewQueue": "No active review queue items.", "previousDay": "Previous day", "nextDay": "Next day", @@ -295,14 +295,14 @@ "week": "Week", "campaignDeadline": "Campaign deadline", "emptyPeriod": "No scheduled items.", - "daySummary": "{content} content items · {projects} campaign deadlines", + "daySummary": "{content} content items · {campaigns} campaign deadlines", "moreItems": "+{count} more", "emptyDayAgenda": "No content is scheduled for this day.", - "projectProgress": "{scheduled} scheduled · {approved} approved", + "campaignProgress": "{scheduled} scheduled · {approved} approved", "missingSchedule": "Needs content scheduled", "noDueDate": "No due date", "labels": { - "unassignedProject": "Unassigned campaign" + "unassignedCampaign": "Unassigned campaign" }, "readiness": { "building": "In production", @@ -339,13 +339,13 @@ "emptyRisks": "No cross-workspace delivery risks right now.", "emptyActivity": "No recent workflow activity yet.", "labels": { - "projects": "campaigns", + "campaigns": "campaigns", "upcoming": "upcoming", "blocked": "blocked" }, "stats": { "workspaces": "Workspaces", - "projects": "Campaigns", + "campaigns": "Campaigns", "upcoming": "Upcoming items", "blockers": "At-risk items" } @@ -372,11 +372,11 @@ "primaryContactPortraitUrl": "Primary contact portrait URL" } }, - "projects": { + "campaigns": { "eyebrow": "Campaign planning", "title": "Campaigns", "description": "Campaigns grouped inside the active workspace by status, date range, and planning notes.", - "newProject": "New campaign", + "newCampaign": "New campaign", "createTitle": "Create campaign", "loading": "Loading campaigns...", "empty": "No campaigns are available for the active workspace.", @@ -444,8 +444,8 @@ "title": "Title", "client": "Client", "selectClient": "Select a client", - "project": "Campaign", - "selectProject": "Select a campaign", + "campaign": "Campaign", + "selectCampaign": "Select a campaign", "dueDate": "Due date", "publicationTargets": "Publication targets", "publicationTargetsPlaceholder": "Instagram Reel, TikTok", diff --git a/frontend/src/locales/fr.json b/frontend/src/locales/fr.json index 2e7c450..087d31f 100644 --- a/frontend/src/locales/fr.json +++ b/frontend/src/locales/fr.json @@ -74,7 +74,7 @@ "myFeedback": "Mon feedback", "feedbackReview": "Revue feedback", "channels": "Canaux", - "projects": "Campagnes", + "campaigns": "Campagnes", "reviewQueue": "File de révision", "content": "Contenu", "profile": "Profil", @@ -210,7 +210,7 @@ "title": "Contexte", "workspace": "Espace", "client": "Client", - "project": "Campagne", + "campaign": "Campagne", "contentItem": "Élément de contenu" }, "activity": { @@ -253,11 +253,11 @@ "sidebar": { "allClients": "Tous les clients", "allChannels": "Tous les canaux", - "allProjects": "Toutes les campagnes", + "allCampaigns": "Toutes les campagnes", "allReviewItems": "File de révision complète", "noClients": "Aucun client pour le moment.", "noChannels": "Aucun canal pour le moment.", - "noProjects": "Aucune campagne pour le moment.", + "noCampaigns": "Aucune campagne pour le moment.", "noReviewItems": "Aucun élément à réviser pour le moment." }, "settings": { @@ -281,12 +281,12 @@ "deliveryRisks": "Ce qui peut glisser", "overdueItems": "Éléments en retard", "approvalBlockers": "En attente d'approbation ou de révision", - "unscheduledProjects": "Campagnes sans contenu planifié", + "unscheduledCampaigns": "Campagnes sans contenu planifié", "reviewQueueSnapshot": "Aperçu de la file de révision", "emptyUpcoming": "Aucun contenu planifié à venir.", "emptyOverdue": "Rien n'est en retard pour le moment.", "emptyApproval": "Aucun blocage d'approbation pour le moment.", - "emptyProjects": "Chaque campagne a au moins un élément de contenu planifié.", + "emptyCampaigns": "Chaque campagne a au moins un élément de contenu planifié.", "emptyReviewQueue": "Aucun élément actif dans la file de révision.", "previousDay": "Jour précédent", "nextDay": "Jour suivant", @@ -295,14 +295,14 @@ "week": "Semaine", "campaignDeadline": "Échéance de campagne", "emptyPeriod": "Aucun élément planifié.", - "daySummary": "{content} contenus · {projects} échéances de campagne", + "daySummary": "{content} contenus · {campaigns} échéances de campagne", "moreItems": "+{count} autres", "emptyDayAgenda": "Aucun contenu n'est planifié pour cette journée.", - "projectProgress": "{scheduled} planifiés · {approved} approuvés", + "campaignProgress": "{scheduled} planifiés · {approved} approuvés", "missingSchedule": "Contenu à planifier", "noDueDate": "Aucune échéance", "labels": { - "unassignedProject": "Campagne non attribuée" + "unassignedCampaign": "Campagne non attribuée" }, "readiness": { "building": "En production", @@ -339,13 +339,13 @@ "emptyRisks": "Aucun risque de livraison multi-espace pour le moment.", "emptyActivity": "Aucune activité récente du workflow.", "labels": { - "projects": "campagnes", + "campaigns": "campagnes", "upcoming": "à venir", "blocked": "bloqués" }, "stats": { "workspaces": "Espaces", - "projects": "Campagnes", + "campaigns": "Campagnes", "upcoming": "Éléments à venir", "blockers": "Éléments à risque" } @@ -372,11 +372,11 @@ "primaryContactPortraitUrl": "URL du portrait du contact principal" } }, - "projects": { + "campaigns": { "eyebrow": "Planification des campagnes", "title": "Campagnes", "description": "Campagnes regroupées dans l'espace actif par statut, plage de dates et notes de planification.", - "newProject": "Nouvelle campagne", + "newCampaign": "Nouvelle campagne", "createTitle": "Créer une campagne", "loading": "Chargement des campagnes...", "empty": "Aucune campagne n'est disponible pour l'espace actif.", @@ -444,8 +444,8 @@ "title": "Titre", "client": "Client", "selectClient": "Sélectionner un client", - "project": "Campagne", - "selectProject": "Sélectionner une campagne", + "campaign": "Campagne", + "selectCampaign": "Sélectionner une campagne", "dueDate": "Date d'échéance", "publicationTargets": "Cibles de publication", "publicationTargetsPlaceholder": "Instagram Reel, TikTok", diff --git a/frontend/src/main.js b/frontend/src/main.js index 79bd05d..02038b3 100644 --- a/frontend/src/main.js +++ b/frontend/src/main.js @@ -29,7 +29,7 @@ import { useWorkspaceStore } from '@/features/workspaces/stores/workspaceStore.j import { useReviewQueueStore } from '@/features/reviews/stores/reviewQueueStore.js'; import { useContentItemsStore } from '@/features/content/stores/contentItemsStore.js'; import { useClientsStore } from '@/features/clients/stores/clientsStore.js'; -import { useProjectsStore } from '@/features/projects/stores/projectsStore.js'; +import { useCampaignsStore } from '@/features/campaigns/stores/campaignsStore.js'; import { useNotificationsStore } from '@/features/notifications/stores/notificationsStore.js'; import { useChannelsStore } from '@/features/channels/stores/channelsStore.js'; import { i18n } from '@/plugins/i18n.js'; @@ -95,7 +95,7 @@ useAuthStore(); useUserProfileStore(); useWorkspaceStore(); useClientsStore(); -useProjectsStore(); +useCampaignsStore(); useChannelsStore(); useReviewQueueStore(); useContentItemsStore(); diff --git a/frontend/src/router/router.js b/frontend/src/router/router.js index 6646d05..24a14a1 100644 --- a/frontend/src/router/router.js +++ b/frontend/src/router/router.js @@ -10,8 +10,8 @@ const VerifyEmailView = () => import('@/features/auth/views/VerifyEmailView.vue' const OverviewView = () => import('@/features/workspaces/views/OverviewView.vue'); const DashboardView = () => import('@/features/workspaces/views/DashboardView.vue'); const ChannelsView = () => import('@/features/channels/views/ChannelsView.vue'); -const CampaignsView = () => import('@/features/projects/views/ProjectsView.vue'); -const CampaignDetailView = () => import('@/features/projects/views/ProjectDetailView.vue'); +const CampaignsView = () => import('@/features/campaigns/views/CampaignsView.vue'); +const CampaignDetailView = () => import('@/features/campaigns/views/CampaignDetailView.vue'); const MediaLibraryView = () => import('@/features/content/views/MediaLibraryView.vue'); const WorkspaceCreateView = () => import('@/features/workspaces/views/WorkspaceCreateView.vue'); const SettingsLayoutView = () => import('@/features/settings/views/SettingsLayoutView.vue'); @@ -67,7 +67,7 @@ const routes = [ meta: { requiresAuth: true }, }, { - path: '/app/campaigns/:projectId', + path: '/app/campaigns/:campaignId', name: 'campaign-detail', component: CampaignDetailView, meta: { requiresAuth: true }, diff --git a/shared/openapi/openapi.json b/shared/openapi/openapi.json index 3b7af07..6e798aa 100644 --- a/shared/openapi/openapi.json +++ b/shared/openapi/openapi.json @@ -385,108 +385,6 @@ ] } }, - "/api/projects": { - "post": { - "tags": [ - "Projects", - "Api" - ], - "operationId": "SocializeApiModulesProjectsHandlersCreateProjectHandler", - "requestBody": { - "x-name": "CreateProjectRequest", - "description": "", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SocializeApiModulesProjectsHandlersCreateProjectRequest" - } - } - }, - "required": true, - "x-position": 1 - }, - "responses": { - "200": { - "description": "Success", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SocializeApiModulesProjectsHandlersProjectDto" - } - } - } - }, - "400": { - "description": "Bad Request", - "content": { - "application/problem+json": { - "schema": { - "$ref": "#/components/schemas/FastEndpointsErrorResponse" - } - } - } - }, - "401": { - "description": "Unauthorized" - } - }, - "security": [ - { - "JWTBearerAuth": [] - } - ] - }, - "get": { - "tags": [ - "Projects", - "Api" - ], - "operationId": "SocializeApiModulesProjectsHandlersGetProjectsHandler", - "parameters": [ - { - "name": "workspaceId", - "in": "query", - "schema": { - "type": "string", - "format": "guid", - "nullable": true - } - }, - { - "name": "clientId", - "in": "query", - "schema": { - "type": "string", - "format": "guid", - "nullable": true - } - } - ], - "responses": { - "200": { - "description": "Success", - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/SocializeApiModulesProjectsHandlersProjectDto" - } - } - } - } - }, - "401": { - "description": "Unauthorized" - } - }, - "security": [ - { - "JWTBearerAuth": [] - } - ] - } - }, "/api/notifications": { "get": { "tags": [ @@ -1983,7 +1881,7 @@ } }, { - "name": "projectId", + "name": "campaignId", "in": "query", "schema": { "type": "string", @@ -2569,6 +2467,108 @@ ] } }, + "/api/campaigns": { + "post": { + "tags": [ + "Campaigns", + "Api" + ], + "operationId": "SocializeApiModulesCampaignsHandlersCreateCampaignHandler", + "requestBody": { + "x-name": "CreateCampaignRequest", + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SocializeApiModulesCampaignsHandlersCreateCampaignRequest" + } + } + }, + "required": true, + "x-position": 1 + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SocializeApiModulesCampaignsHandlersCampaignDto" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/problem+json": { + "schema": { + "$ref": "#/components/schemas/FastEndpointsErrorResponse" + } + } + } + }, + "401": { + "description": "Unauthorized" + } + }, + "security": [ + { + "JWTBearerAuth": [] + } + ] + }, + "get": { + "tags": [ + "Campaigns", + "Api" + ], + "operationId": "SocializeApiModulesCampaignsHandlersGetCampaignsHandler", + "parameters": [ + { + "name": "workspaceId", + "in": "query", + "schema": { + "type": "string", + "format": "guid", + "nullable": true + } + }, + { + "name": "clientId", + "in": "query", + "schema": { + "type": "string", + "format": "guid", + "nullable": true + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SocializeApiModulesCampaignsHandlersCampaignDto" + } + } + } + } + }, + "401": { + "description": "Unauthorized" + } + }, + "security": [ + { + "JWTBearerAuth": [] + } + ] + } + }, "/api/assets/{id}/revisions": { "post": { "tags": [ @@ -3178,105 +3178,6 @@ } } }, - "SocializeApiModulesProjectsHandlersProjectDto": { - "type": "object", - "additionalProperties": false, - "properties": { - "id": { - "type": "string", - "format": "guid" - }, - "workspaceId": { - "type": "string", - "format": "guid" - }, - "clientId": { - "type": "string", - "format": "guid" - }, - "name": { - "type": "string" - }, - "description": { - "type": "string", - "nullable": true - }, - "notes": { - "type": "string", - "nullable": true - }, - "status": { - "type": "string" - }, - "startDate": { - "type": "string", - "format": "date-time" - }, - "endDate": { - "type": "string", - "format": "date-time" - } - } - }, - "SocializeApiModulesProjectsHandlersCreateProjectRequest": { - "type": "object", - "additionalProperties": false, - "required": [ - "workspaceId", - "clientId", - "name", - "startDate", - "endDate" - ], - "properties": { - "workspaceId": { - "type": "string", - "format": "guid", - "minLength": 1, - "nullable": false - }, - "clientId": { - "type": "string", - "format": "guid", - "minLength": 1, - "nullable": false - }, - "name": { - "type": "string", - "maxLength": 256, - "minLength": 0, - "nullable": false - }, - "startDate": { - "type": "string", - "format": "date-time", - "minLength": 1, - "nullable": false - }, - "endDate": { - "type": "string", - "format": "date-time", - "minLength": 1, - "nullable": false - }, - "description": { - "type": "string", - "maxLength": 4000, - "minLength": 0, - "nullable": true - }, - "notes": { - "type": "string", - "maxLength": 4000, - "minLength": 0, - "nullable": true - } - } - }, - "SocializeApiModulesProjectsHandlersGetProjectsRequest": { - "type": "object", - "additionalProperties": false - }, "SocializeApiModulesNotificationsHandlersNotificationEventDto": { "type": "object", "additionalProperties": false, @@ -3464,7 +3365,7 @@ "format": "guid" } }, - "authorizedProjectIds": { + "authorizedCampaignIds": { "type": "array", "items": { "type": "string", @@ -3892,12 +3793,12 @@ "type": "string", "nullable": true }, - "projectId": { + "campaignId": { "type": "string", "format": "guid", "nullable": true }, - "projectName": { + "campaignName": { "type": "string", "nullable": true }, @@ -4041,12 +3942,12 @@ "minLength": 0, "nullable": true }, - "projectId": { + "campaignId": { "type": "string", "format": "guid", "nullable": true }, - "projectName": { + "campaignName": { "type": "string", "maxLength": 256, "minLength": 0, @@ -4108,7 +4009,7 @@ "type": "string", "format": "guid" }, - "projectId": { + "campaignId": { "type": "string", "format": "guid" }, @@ -4148,7 +4049,7 @@ "required": [ "workspaceId", "clientId", - "projectId", + "campaignId", "title", "publicationMessage", "publicationTargets" @@ -4166,7 +4067,7 @@ "minLength": 1, "nullable": false }, - "projectId": { + "campaignId": { "type": "string", "format": "guid", "minLength": 1, @@ -4307,7 +4208,7 @@ "type": "string", "format": "guid" }, - "projectId": { + "campaignId": { "type": "string", "format": "guid" }, @@ -4614,6 +4515,105 @@ } } }, + "SocializeApiModulesCampaignsHandlersCampaignDto": { + "type": "object", + "additionalProperties": false, + "properties": { + "id": { + "type": "string", + "format": "guid" + }, + "workspaceId": { + "type": "string", + "format": "guid" + }, + "clientId": { + "type": "string", + "format": "guid" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string", + "nullable": true + }, + "notes": { + "type": "string", + "nullable": true + }, + "status": { + "type": "string" + }, + "startDate": { + "type": "string", + "format": "date-time" + }, + "endDate": { + "type": "string", + "format": "date-time" + } + } + }, + "SocializeApiModulesCampaignsHandlersCreateCampaignRequest": { + "type": "object", + "additionalProperties": false, + "required": [ + "workspaceId", + "clientId", + "name", + "startDate", + "endDate" + ], + "properties": { + "workspaceId": { + "type": "string", + "format": "guid", + "minLength": 1, + "nullable": false + }, + "clientId": { + "type": "string", + "format": "guid", + "minLength": 1, + "nullable": false + }, + "name": { + "type": "string", + "maxLength": 256, + "minLength": 0, + "nullable": false + }, + "startDate": { + "type": "string", + "format": "date-time", + "minLength": 1, + "nullable": false + }, + "endDate": { + "type": "string", + "format": "date-time", + "minLength": 1, + "nullable": false + }, + "description": { + "type": "string", + "maxLength": 4000, + "minLength": 0, + "nullable": true + }, + "notes": { + "type": "string", + "maxLength": 4000, + "minLength": 0, + "nullable": true + } + } + }, + "SocializeApiModulesCampaignsHandlersGetCampaignsRequest": { + "type": "object", + "additionalProperties": false + }, "SocializeApiModulesAssetsHandlersAssetRevisionDto": { "type": "object", "additionalProperties": false,