Merge branch 'approval-workflow-docs' into HEAD
# Conflicts: # frontend/src/api/schema.d.ts # frontend/src/features/content/views/ContentItemDetailView.vue # frontend/src/features/workspaces/views/DashboardView.vue # shared/openapi/openapi.json
This commit is contained in:
@@ -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<Workspace> Workspaces => Set<Workspace>();
|
||||
public DbSet<WorkspaceInvite> WorkspaceInvites => Set<WorkspaceInvite>();
|
||||
public DbSet<Client> Clients => Set<Client>();
|
||||
public DbSet<Project> Projects => Set<Project>();
|
||||
public DbSet<Campaign> Campaigns => Set<Campaign>();
|
||||
public DbSet<ContentItem> ContentItems => Set<ContentItem>();
|
||||
public DbSet<ContentItemRevision> ContentItemRevisions => Set<ContentItemRevision>();
|
||||
public DbSet<Asset> Assets => Set<Asset>();
|
||||
@@ -43,7 +43,7 @@ public class AppDbContext(
|
||||
|
||||
builder.ConfigureWorkspacesModule();
|
||||
builder.ConfigureClientsModule();
|
||||
builder.ConfigureProjectsModule();
|
||||
builder.ConfigureCampaignsModule();
|
||||
builder.ConfigureContentItemsModule();
|
||||
builder.ConfigureAssetsModule();
|
||||
builder.ConfigureCommentsModule();
|
||||
|
||||
@@ -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<Claim> existingClaims = await userManager.GetClaimsAsync(user);
|
||||
List<Claim> 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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,9 +23,9 @@ public static class ClaimsPrincipalExtensions
|
||||
return claims.GetScopeIds(KnownClaims.ClientScope);
|
||||
}
|
||||
|
||||
public static IReadOnlyCollection<Guid> GetProjectScopeIds(this ClaimsPrincipal claims)
|
||||
public static IReadOnlyCollection<Guid> GetCampaignScopeIds(this ClaimsPrincipal claims)
|
||||
{
|
||||
return claims.GetScopeIds(KnownClaims.ProjectScope);
|
||||
return claims.GetScopeIds(KnownClaims.CampaignScope);
|
||||
}
|
||||
|
||||
public static string? GetPersona(this ClaimsPrincipal claims)
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
1423
backend/src/Socialize.Api/Migrations/20260501191447_RenameProjectsToCampaigns.Designer.cs
generated
Normal file
1423
backend/src/Socialize.Api/Migrations/20260501191447_RenameProjectsToCampaigns.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,117 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Socialize.Api.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class RenameProjectsToCampaigns : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
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");
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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");
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -438,6 +438,59 @@ namespace Socialize.Api.Migrations
|
||||
b.ToTable("AssetRevisions", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Socialize.Api.Modules.Campaigns.Data.Campaign", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid>("ClientId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(4000)
|
||||
.HasColumnType("character varying(4000)");
|
||||
|
||||
b.Property<DateTimeOffset>("EndDate")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("Notes")
|
||||
.HasMaxLength(4000)
|
||||
.HasColumnType("character varying(4000)");
|
||||
|
||||
b.Property<DateTimeOffset>("StartDate")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Status")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<Guid>("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<Guid>("Id")
|
||||
@@ -549,6 +602,9 @@ namespace Socialize.Api.Migrations
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid>("CampaignId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid>("ClientId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
@@ -572,9 +628,6 @@ namespace Socialize.Api.Migrations
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)");
|
||||
|
||||
b.Property<Guid>("ProjectId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("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<Guid?>("CampaignId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("CampaignName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("CancellationReason")
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
@@ -821,13 +881,6 @@ namespace Socialize.Api.Migrations
|
||||
b.Property<DateTimeOffset>("LastActivityAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<Guid?>("ProjectId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("ProjectName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("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<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid>("ClientId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(4000)
|
||||
.HasColumnType("character varying(4000)");
|
||||
|
||||
b.Property<DateTimeOffset>("EndDate")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("Notes")
|
||||
.HasMaxLength(4000)
|
||||
.HasColumnType("character varying(4000)");
|
||||
|
||||
b.Property<DateTimeOffset>("StartDate")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Status")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<Guid>("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<Guid>("Id")
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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; }
|
||||
@@ -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 =>
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<CreateProjectRequest>
|
||||
public class CreateCampaignRequestValidator
|
||||
: Validator<CreateCampaignRequest>
|
||||
{
|
||||
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<CreateProjectRequest, ProjectDto>
|
||||
: Endpoint<CreateCampaignRequest, CampaignDto>
|
||||
{
|
||||
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);
|
||||
}
|
||||
@@ -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<GetCampaignsRequest, IReadOnlyCollection<CampaignDto>>
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
Get("/api/campaigns");
|
||||
Options(o => o.WithTags("Campaigns"));
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(GetCampaignsRequest request, CancellationToken ct)
|
||||
{
|
||||
IQueryable<Campaign> query = dbContext.Campaigns.AsQueryable();
|
||||
|
||||
if (accessScopeService.IsManager(User))
|
||||
{
|
||||
if (request.WorkspaceId.HasValue)
|
||||
{
|
||||
query = query.Where(campaign => campaign.WorkspaceId == request.WorkspaceId.Value);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
IReadOnlyCollection<Guid> workspaceScopeIds = User.GetWorkspaceScopeIds();
|
||||
IReadOnlyCollection<Guid> clientScopeIds = User.GetClientScopeIds();
|
||||
IReadOnlyCollection<Guid> 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<CampaignDto> 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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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<ContentItemRevision>(revision =>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<Guid> workspaceScopeIds = User.GetWorkspaceScopeIds();
|
||||
IReadOnlyCollection<Guid> clientScopeIds = User.GetClientScopeIds();
|
||||
IReadOnlyCollection<Guid> projectScopeIds = User.GetProjectScopeIds();
|
||||
IReadOnlyCollection<Guid> 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,
|
||||
|
||||
@@ -145,7 +145,7 @@ public class UpdateContentItemStatusHandler(
|
||||
item.Id,
|
||||
item.WorkspaceId,
|
||||
item.ClientId,
|
||||
item.ProjectId,
|
||||
item.CampaignId,
|
||||
item.Title,
|
||||
item.PublicationMessage,
|
||||
item.PublicationTargets,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -50,8 +50,8 @@ public class GetCurrentUserQueryHandler(
|
||||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
List<Guid> projectIds = claims
|
||||
.Where(claim => claim.Type == KnownClaims.ProjectScope)
|
||||
List<Guid> 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,
|
||||
|
||||
@@ -7,7 +7,7 @@ public class UserDto
|
||||
public string? Persona { get; init; }
|
||||
public IList<Guid> AuthorizedWorkspaceIds { get; init; } = [];
|
||||
public IList<Guid> AuthorizedClientIds { get; init; } = [];
|
||||
public IList<Guid> AuthorizedProjectIds { get; init; } = [];
|
||||
public IList<Guid> AuthorizedCampaignIds { get; init; } = [];
|
||||
public string Username { get; init; } = null!;
|
||||
public string? Alias { get; init; }
|
||||
public string? PortraitUrl { get; init; }
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 =>
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<GetProjectsRequest, IReadOnlyCollection<ProjectDto>>
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
Get("/api/projects");
|
||||
Options(o => o.WithTags("Projects"));
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(GetProjectsRequest request, CancellationToken ct)
|
||||
{
|
||||
IQueryable<Project> query = dbContext.Projects.AsQueryable();
|
||||
|
||||
if (accessScopeService.IsManager(User))
|
||||
{
|
||||
if (request.WorkspaceId.HasValue)
|
||||
{
|
||||
query = query.Where(project => project.WorkspaceId == request.WorkspaceId.Value);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
IReadOnlyCollection<Guid> workspaceScopeIds = User.GetWorkspaceScopeIds();
|
||||
IReadOnlyCollection<Guid> clientScopeIds = User.GetClientScopeIds();
|
||||
IReadOnlyCollection<Guid> 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<ProjectDto> 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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,60 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2"/>
|
||||
</startup>
|
||||
<runtime>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.Build" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-15.1.0.0" newVersion="15.1.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.Build.Framework" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-15.1.0.0" newVersion="15.1.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.Build.Utilities.Core" publicKeyToken="b03f5f7f11d50a3a"
|
||||
culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-15.1.0.0" newVersion="15.1.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.Build.Tasks.Core" publicKeyToken="b03f5f7f11d50a3a"
|
||||
culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-15.1.0.0" newVersion="15.1.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.IO.Redist" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.1" newVersion="6.0.0.1"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Collections.Immutable" publicKeyToken="b03f5f7f11d50a3a"
|
||||
culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-9.0.0.0" newVersion="9.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Memory" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.0.1.2" newVersion="4.0.1.2"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a"
|
||||
culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
</configuration>
|
||||
Binary file not shown.
BIN
backend/src/Socialize.Api/bin\Debug/net10.0/BuildHost-net472/Newtonsoft.Json.dll
Executable file
BIN
backend/src/Socialize.Api/bin\Debug/net10.0/BuildHost-net472/Newtonsoft.Json.dll
Executable file
Binary file not shown.
BIN
backend/src/Socialize.Api/bin\Debug/net10.0/BuildHost-net472/System.Buffers.dll
Executable file
BIN
backend/src/Socialize.Api/bin\Debug/net10.0/BuildHost-net472/System.Buffers.dll
Executable file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
backend/src/Socialize.Api/bin\Debug/net10.0/BuildHost-net472/System.Memory.dll
Executable file
BIN
backend/src/Socialize.Api/bin\Debug/net10.0/BuildHost-net472/System.Memory.dll
Executable file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,659 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<runtime>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.Build" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-15.1.0.0" newVersion="15.1.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.Build.Framework" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-15.1.0.0" newVersion="15.1.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.Build.Utilities.Core" publicKeyToken="b03f5f7f11d50a3a"
|
||||
culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-15.1.0.0" newVersion="15.1.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.Build.Tasks.Core" publicKeyToken="b03f5f7f11d50a3a"
|
||||
culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-15.1.0.0" newVersion="15.1.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Collections.Immutable" publicKeyToken="b03f5f7f11d50a3a"
|
||||
culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-9.0.0.0" newVersion="9.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.VisualBasic.Core" publicKeyToken="b03f5f7f11d50a3a"
|
||||
culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-11.0.0.0" newVersion="11.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.Win32.Primitives" publicKeyToken="b03f5f7f11d50a3a"
|
||||
culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.Win32.Registry" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Buffers" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Collections.Concurrent" publicKeyToken="b03f5f7f11d50a3a"
|
||||
culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Collections.NonGeneric" publicKeyToken="b03f5f7f11d50a3a"
|
||||
culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Collections.Specialized" publicKeyToken="b03f5f7f11d50a3a"
|
||||
culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Collections" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.ComponentModel.Annotations" publicKeyToken="b03f5f7f11d50a3a"
|
||||
culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.ComponentModel.EventBasedAsync" publicKeyToken="b03f5f7f11d50a3a"
|
||||
culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.ComponentModel.Primitives" publicKeyToken="b03f5f7f11d50a3a"
|
||||
culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.ComponentModel.TypeConverter" publicKeyToken="b03f5f7f11d50a3a"
|
||||
culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.ComponentModel" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Console" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Data.Common" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Diagnostics.Contracts" publicKeyToken="b03f5f7f11d50a3a"
|
||||
culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Diagnostics.FileVersionInfo" publicKeyToken="b03f5f7f11d50a3a"
|
||||
culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Diagnostics.Process" publicKeyToken="b03f5f7f11d50a3a"
|
||||
culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Diagnostics.StackTrace" publicKeyToken="b03f5f7f11d50a3a"
|
||||
culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Diagnostics.TextWriterTraceListener" publicKeyToken="b03f5f7f11d50a3a"
|
||||
culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Diagnostics.TraceSource" publicKeyToken="b03f5f7f11d50a3a"
|
||||
culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Diagnostics.Tracing" publicKeyToken="b03f5f7f11d50a3a"
|
||||
culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Drawing.Primitives" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.IO.Compression.ZipFile" publicKeyToken="b77a5c561934e089"
|
||||
culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.IO.Compression" publicKeyToken="b77a5c561934e089" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.IO.FileSystem.AccessControl" publicKeyToken="b03f5f7f11d50a3a"
|
||||
culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.IO.FileSystem.DriveInfo" publicKeyToken="b03f5f7f11d50a3a"
|
||||
culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.IO.FileSystem.Watcher" publicKeyToken="b03f5f7f11d50a3a"
|
||||
culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.IO.IsolatedStorage" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.IO.MemoryMappedFiles" publicKeyToken="b03f5f7f11d50a3a"
|
||||
culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.IO.Pipes.AccessControl" publicKeyToken="b03f5f7f11d50a3a"
|
||||
culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.IO.Pipes" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Linq.Expressions" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Linq.Parallel" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Linq.Queryable" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Linq" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Memory" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Net.HttpListener" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Net.Mail" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Net.NameResolution" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Net.NetworkInformation" publicKeyToken="b03f5f7f11d50a3a"
|
||||
culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Net.Ping" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Net.Primitives" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Net.Requests" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Net.Security" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Net.ServicePoint" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Net.Sockets" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Net.WebClient" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Net.WebHeaderCollection" publicKeyToken="b03f5f7f11d50a3a"
|
||||
culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Net.WebProxy" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Net.WebSockets.Client" publicKeyToken="b03f5f7f11d50a3a"
|
||||
culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Net.WebSockets" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Numerics.Vectors" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.ObjectModel" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Reflection.Emit.ILGeneration" publicKeyToken="b03f5f7f11d50a3a"
|
||||
culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Reflection.Emit.Lightweight" publicKeyToken="b03f5f7f11d50a3a"
|
||||
culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Reflection.Emit" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Reflection.Primitives" publicKeyToken="b03f5f7f11d50a3a"
|
||||
culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Resources.Writer" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Runtime.CompilerServices.VisualC" publicKeyToken="b03f5f7f11d50a3a"
|
||||
culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Runtime.InteropServices.RuntimeInformation"
|
||||
publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Runtime.InteropServices" publicKeyToken="b03f5f7f11d50a3a"
|
||||
culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Runtime.Numerics" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Runtime.Serialization.Formatters" publicKeyToken="b03f5f7f11d50a3a"
|
||||
culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Runtime.Serialization.Json" publicKeyToken="b03f5f7f11d50a3a"
|
||||
culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Runtime.Serialization.Primitives" publicKeyToken="b03f5f7f11d50a3a"
|
||||
culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Runtime.Serialization.Xml" publicKeyToken="b03f5f7f11d50a3a"
|
||||
culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Runtime" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Security.AccessControl" publicKeyToken="b03f5f7f11d50a3a"
|
||||
culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Security.Claims" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Security.Cryptography.Algorithms" publicKeyToken="b03f5f7f11d50a3a"
|
||||
culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Security.Cryptography.Cng" publicKeyToken="b03f5f7f11d50a3a"
|
||||
culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Security.Cryptography.Csp" publicKeyToken="b03f5f7f11d50a3a"
|
||||
culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Security.Cryptography.Encoding" publicKeyToken="b03f5f7f11d50a3a"
|
||||
culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Security.Cryptography.Primitives" publicKeyToken="b03f5f7f11d50a3a"
|
||||
culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Security.Cryptography.X509Certificates" publicKeyToken="b03f5f7f11d50a3a"
|
||||
culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Security.Principal.Windows" publicKeyToken="b03f5f7f11d50a3a"
|
||||
culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Text.Encoding.Extensions" publicKeyToken="b03f5f7f11d50a3a"
|
||||
culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Text.RegularExpressions" publicKeyToken="b03f5f7f11d50a3a"
|
||||
culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Threading.Overlapped" publicKeyToken="b03f5f7f11d50a3a"
|
||||
culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Threading.Tasks.Parallel" publicKeyToken="b03f5f7f11d50a3a"
|
||||
culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Threading.Thread" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Threading.ThreadPool" publicKeyToken="b03f5f7f11d50a3a"
|
||||
culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Threading" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Transactions.Local" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Web.HttpUtility" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Xml.ReaderWriter" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Xml.XDocument" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Xml.XPath.XDocument" publicKeyToken="b03f5f7f11d50a3a"
|
||||
culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Xml.XPath" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Xml.XmlSerializer" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="netstandard" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-2.1.0.0" newVersion="2.1.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Configuration.ConfigurationManager" publicKeyToken="cc7b13ffcd2ddd51"
|
||||
culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Security.Cryptography.Xml" publicKeyToken="cc7b13ffcd2ddd51"
|
||||
culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.CodeDom" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral"/>
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
</configuration>
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
25
docs/TASKS/campaigns/001-rename-projects-to-campaigns.md
Normal file
25
docs/TASKS/campaigns/001-rename-projects-to-campaigns.md
Normal file
@@ -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
|
||||
```
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
16
frontend/docs/claims-and-roles.md
Normal file
16
frontend/docs/claims-and-roles.md
Normal file
@@ -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.
|
||||
250
frontend/src/api/schema.d.ts
vendored
250
frontend/src/api/schema.d.ts
vendored
@@ -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<string, never>;
|
||||
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<string, never>;
|
||||
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;
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
});
|
||||
@@ -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));
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="page-shell">
|
||||
<div
|
||||
v-if="!project"
|
||||
v-if="!campaign"
|
||||
class="page-message error"
|
||||
>
|
||||
The selected campaign could not be found in the active workspace.
|
||||
@@ -66,21 +66,21 @@
|
||||
Campaigns
|
||||
</router-link>
|
||||
</div>
|
||||
<h1>{{ project.name }}</h1>
|
||||
<p>{{ project.description || `${workspaceStore.activeWorkspace?.name} delivery stream with only the content scheduled in this campaign.` }}</p>
|
||||
<h1>{{ campaign.name }}</h1>
|
||||
<p>{{ campaign.description || `${workspaceStore.activeWorkspace?.name} delivery stream with only the content scheduled in this campaign.` }}</p>
|
||||
</div>
|
||||
|
||||
<div class="hero-meta">
|
||||
<div class="meta-chip">{{ project.status }}</div>
|
||||
<div class="meta-copy">{{ formatProjectDateRange(project) }}</div>
|
||||
<div class="meta-chip">{{ campaign.status }}</div>
|
||||
<div class="meta-copy">{{ formatCampaignDateRange(campaign) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="project.notes"
|
||||
v-if="campaign.notes"
|
||||
class="page-message"
|
||||
>
|
||||
{{ project.notes }}
|
||||
{{ campaign.notes }}
|
||||
</div>
|
||||
|
||||
<div class="section-header">
|
||||
@@ -91,10 +91,10 @@
|
||||
<div class="scope-actions">
|
||||
<router-link
|
||||
v-if="authStore.isManager || authStore.isProvider"
|
||||
:to="{ name: 'content-item-create', query: { projectId: project.id } }"
|
||||
:to="{ name: 'content-item-create', query: { campaignId: campaign.id } }"
|
||||
class="scope-button"
|
||||
>
|
||||
New content in {{ project.name }}
|
||||
New content in {{ campaign.name }}
|
||||
</router-link>
|
||||
</div>
|
||||
|
||||
@@ -5,13 +5,13 @@
|
||||
import { useAuthStore } from '@/features/auth/stores/authStore.js';
|
||||
import { useClientsStore } from '@/features/clients/stores/clientsStore.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';
|
||||
|
||||
const route = useRoute();
|
||||
const authStore = useAuthStore();
|
||||
const workspaceStore = useWorkspaceStore();
|
||||
const clientsStore = useClientsStore();
|
||||
const projectsStore = useProjectsStore();
|
||||
const campaignsStore = useCampaignsStore();
|
||||
const { t } = useI18n();
|
||||
const isCreateFormVisible = ref(false);
|
||||
const formError = ref(null);
|
||||
@@ -41,29 +41,29 @@
|
||||
}
|
||||
|
||||
async function submitForm() {
|
||||
if (projectsStore.isCreating) {
|
||||
if (campaignsStore.isCreating) {
|
||||
return;
|
||||
}
|
||||
|
||||
formError.value = null;
|
||||
|
||||
if (!form.name || !form.startDate || !form.endDate) {
|
||||
formError.value = t('projects.errors.required');
|
||||
formError.value = t('campaigns.errors.required');
|
||||
return;
|
||||
}
|
||||
|
||||
if (new Date(form.endDate) < new Date(form.startDate)) {
|
||||
formError.value = t('projects.errors.invalidDateRange');
|
||||
formError.value = t('campaigns.errors.invalidDateRange');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!operationalClient.value?.id) {
|
||||
formError.value = t('projects.errors.workspaceAccountRequired');
|
||||
formError.value = t('campaigns.errors.workspaceAccountRequired');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await projectsStore.createProject({
|
||||
await campaignsStore.createCampaign({
|
||||
clientId: operationalClient.value.id,
|
||||
name: form.name,
|
||||
startDate: new Date(form.startDate).toISOString(),
|
||||
@@ -75,7 +75,7 @@
|
||||
isCreateFormVisible.value = false;
|
||||
resetForm();
|
||||
} catch (error) {
|
||||
formError.value = t('projects.errors.createFailed');
|
||||
formError.value = t('campaigns.errors.createFailed');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,13 +89,13 @@
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
function formatProjectDateRange(project) {
|
||||
if (!project?.startDate || !project?.endDate) {
|
||||
return t('projects.noDateRange');
|
||||
function formatCampaignDateRange(campaign) {
|
||||
if (!campaign?.startDate || !campaign?.endDate) {
|
||||
return t('campaigns.noDateRange');
|
||||
}
|
||||
|
||||
const start = new Date(project.startDate);
|
||||
const end = new Date(project.endDate);
|
||||
const start = new Date(campaign.startDate);
|
||||
const end = new Date(campaign.endDate);
|
||||
return new Intl.DateTimeFormat(undefined, {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
@@ -108,9 +108,9 @@
|
||||
<section class="page-shell">
|
||||
<div class="header">
|
||||
<div>
|
||||
<div class="eyebrow">{{ t('projects.eyebrow') }}</div>
|
||||
<h1>{{ t('projects.title') }}</h1>
|
||||
<p>{{ t('projects.description') }}</p>
|
||||
<div class="eyebrow">{{ t('campaigns.eyebrow') }}</div>
|
||||
<h1>{{ t('campaigns.title') }}</h1>
|
||||
<p>{{ t('campaigns.description') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -120,7 +120,7 @@
|
||||
class="create-button"
|
||||
@click="openCreateForm"
|
||||
>
|
||||
{{ t('projects.newProject') }}
|
||||
{{ t('campaigns.newCampaign') }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -129,7 +129,7 @@
|
||||
class="create-panel"
|
||||
>
|
||||
<div class="panel-header">
|
||||
<strong>{{ t('projects.createTitle') }}</strong>
|
||||
<strong>{{ t('campaigns.createTitle') }}</strong>
|
||||
<span>{{ workspaceStore.activeWorkspace?.name }}</span>
|
||||
</div>
|
||||
|
||||
@@ -142,45 +142,45 @@
|
||||
|
||||
<div class="form-grid">
|
||||
<label class="field">
|
||||
<span>{{ t('projects.fields.startDate') }}</span>
|
||||
<span>{{ t('campaigns.fields.startDate') }}</span>
|
||||
<input
|
||||
v-model="form.startDate"
|
||||
type="date"
|
||||
:disabled="projectsStore.isCreating"
|
||||
:disabled="campaignsStore.isCreating"
|
||||
/>
|
||||
</label>
|
||||
|
||||
<label class="field">
|
||||
<span>{{ t('projects.fields.endDate') }}</span>
|
||||
<span>{{ t('campaigns.fields.endDate') }}</span>
|
||||
<input
|
||||
v-model="form.endDate"
|
||||
type="date"
|
||||
:disabled="projectsStore.isCreating"
|
||||
:disabled="campaignsStore.isCreating"
|
||||
/>
|
||||
</label>
|
||||
|
||||
<label class="field field-wide">
|
||||
<span>{{ t('projects.fields.name') }}</span>
|
||||
<span>{{ t('campaigns.fields.name') }}</span>
|
||||
<input
|
||||
v-model="form.name"
|
||||
type="text"
|
||||
:disabled="projectsStore.isCreating"
|
||||
:disabled="campaignsStore.isCreating"
|
||||
/>
|
||||
</label>
|
||||
|
||||
<label class="field field-wide">
|
||||
<span>{{ t('projects.fields.description') }}</span>
|
||||
<span>{{ t('campaigns.fields.description') }}</span>
|
||||
<textarea
|
||||
v-model="form.description"
|
||||
:disabled="projectsStore.isCreating"
|
||||
:disabled="campaignsStore.isCreating"
|
||||
></textarea>
|
||||
</label>
|
||||
|
||||
<label class="field field-wide">
|
||||
<span>{{ t('projects.fields.notes') }}</span>
|
||||
<span>{{ t('campaigns.fields.notes') }}</span>
|
||||
<textarea
|
||||
v-model="form.notes"
|
||||
:disabled="projectsStore.isCreating"
|
||||
:disabled="campaignsStore.isCreating"
|
||||
></textarea>
|
||||
</label>
|
||||
</div>
|
||||
@@ -188,64 +188,64 @@
|
||||
<div class="panel-actions">
|
||||
<button
|
||||
class="secondary"
|
||||
:disabled="projectsStore.isCreating"
|
||||
:disabled="campaignsStore.isCreating"
|
||||
@click="isCreateFormVisible = false"
|
||||
>
|
||||
{{ t('common.cancel') }}
|
||||
</button>
|
||||
<button
|
||||
class="primary"
|
||||
:disabled="projectsStore.isCreating"
|
||||
:disabled="campaignsStore.isCreating"
|
||||
@click="submitForm"
|
||||
>
|
||||
<v-progress-circular
|
||||
v-if="projectsStore.isCreating"
|
||||
v-if="campaignsStore.isCreating"
|
||||
indeterminate
|
||||
:size="16"
|
||||
:width="2"
|
||||
/>
|
||||
<span>{{ projectsStore.isCreating ? t('common.creating') : t('projects.createTitle') }}</span>
|
||||
<span>{{ campaignsStore.isCreating ? t('common.creating') : t('campaigns.createTitle') }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="projectsStore.isLoading"
|
||||
v-if="campaignsStore.isLoading"
|
||||
class="page-message"
|
||||
>
|
||||
{{ t('projects.loading') }}
|
||||
{{ t('campaigns.loading') }}
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-else-if="projectsStore.error"
|
||||
v-else-if="campaignsStore.error"
|
||||
class="page-message error"
|
||||
>
|
||||
{{ projectsStore.error }}
|
||||
{{ campaignsStore.error }}
|
||||
</div>
|
||||
|
||||
<div class="project-stack">
|
||||
<div class="campaign-stack">
|
||||
<router-link
|
||||
v-for="project in projectsStore.projects"
|
||||
:key="project.id"
|
||||
:to="{ name: 'campaign-detail', params: { projectId: project.id } }"
|
||||
class="project-row"
|
||||
v-for="campaign in campaignsStore.campaigns"
|
||||
:key="campaign.id"
|
||||
:to="{ name: 'campaign-detail', params: { campaignId: campaign.id } }"
|
||||
class="campaign-row"
|
||||
>
|
||||
<div>
|
||||
<strong>{{ project.name }}</strong>
|
||||
<span>{{ project.description || project.status }}</span>
|
||||
<strong>{{ campaign.name }}</strong>
|
||||
<span>{{ campaign.description || campaign.status }}</span>
|
||||
</div>
|
||||
<div class="project-meta">
|
||||
<div class="campaign-meta">
|
||||
<span>{{ workspaceStore.activeWorkspace?.name || t('nav.noWorkspace') }}</span>
|
||||
<em>{{ formatProjectDateRange(project) }}</em>
|
||||
<em>{{ formatCampaignDateRange(campaign) }}</em>
|
||||
</div>
|
||||
</router-link>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="!projectsStore.isLoading && !projectsStore.projects.length"
|
||||
v-if="!campaignsStore.isLoading && !campaignsStore.campaigns.length"
|
||||
class="page-message"
|
||||
>
|
||||
{{ t('projects.empty') }}
|
||||
{{ t('campaigns.empty') }}
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 @@
|
||||
<div class="hero-meta">
|
||||
<span class="hero-status">{{ client.status }}</span>
|
||||
</div>
|
||||
<p>The client area scopes projects and content so review stays inside one account.</p>
|
||||
<p>The client area scopes campaigns and content so review stays inside one account.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stats-grid">
|
||||
<article class="stat-card">
|
||||
<span>Current campaigns</span>
|
||||
<strong>{{ currentProjects.length }}</strong>
|
||||
<strong>{{ currentCampaigns.length }}</strong>
|
||||
</article>
|
||||
<article class="stat-card">
|
||||
<span>Past campaigns</span>
|
||||
<strong>{{ pastProjects.length }}</strong>
|
||||
<strong>{{ pastCampaigns.length }}</strong>
|
||||
</article>
|
||||
<article class="stat-card">
|
||||
<span>Total content items</span>
|
||||
@@ -420,26 +420,26 @@
|
||||
<div class="section">
|
||||
<div class="section-header">
|
||||
<strong>Current campaigns</strong>
|
||||
<span>{{ currentProjects.length }} active</span>
|
||||
<span>{{ currentCampaigns.length }} active</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="currentProjects.length"
|
||||
class="project-list"
|
||||
v-if="currentCampaigns.length"
|
||||
class="campaign-list"
|
||||
>
|
||||
<router-link
|
||||
v-for="project in currentProjects"
|
||||
:key="project.id"
|
||||
:to="{ name: 'client-project-detail', params: { clientId: client.id, projectId: project.id } }"
|
||||
class="project-card"
|
||||
v-for="campaign in currentCampaigns"
|
||||
:key="campaign.id"
|
||||
:to="{ name: 'campaign-detail', params: { campaignId: campaign.id } }"
|
||||
class="campaign-card"
|
||||
>
|
||||
<div>
|
||||
<strong>{{ project.name }}</strong>
|
||||
<span>{{ project.status }}</span>
|
||||
<strong>{{ campaign.name }}</strong>
|
||||
<span>{{ campaign.status }}</span>
|
||||
</div>
|
||||
<div class="project-meta">
|
||||
<small>{{ itemCountByProjectId.get(project.id) ?? 0 }} content items</small>
|
||||
<em>{{ formatProjectDateRange(project) }}</em>
|
||||
<div class="campaign-meta">
|
||||
<small>{{ itemCountByCampaignId.get(campaign.id) ?? 0 }} content items</small>
|
||||
<em>{{ formatCampaignDateRange(campaign) }}</em>
|
||||
</div>
|
||||
</router-link>
|
||||
</div>
|
||||
@@ -454,26 +454,26 @@
|
||||
<div class="section">
|
||||
<div class="section-header">
|
||||
<strong>Past campaigns</strong>
|
||||
<span>{{ pastProjects.length }} archived or completed</span>
|
||||
<span>{{ pastCampaigns.length }} archived or completed</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="pastProjects.length"
|
||||
class="project-list"
|
||||
v-if="pastCampaigns.length"
|
||||
class="campaign-list"
|
||||
>
|
||||
<router-link
|
||||
v-for="project in pastProjects"
|
||||
:key="project.id"
|
||||
:to="{ name: 'client-project-detail', params: { clientId: client.id, projectId: project.id } }"
|
||||
class="project-card muted"
|
||||
v-for="campaign in pastCampaigns"
|
||||
:key="campaign.id"
|
||||
:to="{ name: 'campaign-detail', params: { campaignId: campaign.id } }"
|
||||
class="campaign-card muted"
|
||||
>
|
||||
<div>
|
||||
<strong>{{ project.name }}</strong>
|
||||
<span>{{ project.status }}</span>
|
||||
<strong>{{ campaign.name }}</strong>
|
||||
<span>{{ campaign.status }}</span>
|
||||
</div>
|
||||
<div class="project-meta">
|
||||
<small>{{ itemCountByProjectId.get(project.id) ?? 0 }} content items</small>
|
||||
<em>{{ formatProjectDateRange(project) }}</em>
|
||||
<div class="campaign-meta">
|
||||
<small>{{ itemCountByCampaignId.get(campaign.id) ?? 0 }} content items</small>
|
||||
<em>{{ formatCampaignDateRange(campaign) }}</em>
|
||||
</div>
|
||||
</router-link>
|
||||
</div>
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ export const useContentItemsStore = defineStore('content-items', () => {
|
||||
params: {
|
||||
workspaceId: workspaceStore.activeWorkspaceId,
|
||||
clientId: filters.clientId,
|
||||
projectId: filters.projectId,
|
||||
campaignId: filters.campaignId,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user