Compare commits
12 Commits
approval-w
...
664eb07201
| Author | SHA1 | Date | |
|---|---|---|---|
| 664eb07201 | |||
| 58c1301054 | |||
| 552f4f1f21 | |||
| 8f4b95f311 | |||
| 4fba72e99c | |||
| 55d8acef4c | |||
| 7d3f495472 | |||
| 802668fb0b | |||
| cd6f402d9e | |||
| 9bdef978bd | |||
| 2d472892d6 | |||
| 884ca4b96d |
@@ -70,6 +70,7 @@ Update OpenAPI:
|
||||
## Current Domain Modules
|
||||
|
||||
- `Identity`: authentication, refresh tokens, email verification, password reset, social login.
|
||||
- `Organizations`: SaaS account ownership, billing/subscription boundary, organization membership, connectors, data mappings, and owned workspaces.
|
||||
- `Workspaces`: workspace membership, workspace settings, access scoping.
|
||||
- `Clients`: client records and primary contacts tied to workspaces.
|
||||
- `Projects`: project pipeline and client/project relationships.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Socialize
|
||||
|
||||
Socialize is a workspace-based workflow application for social media content review, revision, approval, and publication readiness.
|
||||
Socialize is an organization-owned, workspace-based workflow application for social media content review, revision, approval, and publication readiness.
|
||||
|
||||
It is not a public social network. The product is for internal teams, providers, and client approvers coordinating content work before publication.
|
||||
|
||||
|
||||
@@ -8,7 +8,8 @@ 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.Organizations.Data;
|
||||
using Socialize.Api.Modules.Workspaces.Data;
|
||||
|
||||
namespace Socialize.Api.Data;
|
||||
@@ -17,10 +18,12 @@ public class AppDbContext(
|
||||
DbContextOptions<AppDbContext> options)
|
||||
: IdentityDbContext<User, Role, Guid>(options)
|
||||
{
|
||||
public DbSet<Organization> Organizations => Set<Organization>();
|
||||
public DbSet<OrganizationMembership> OrganizationMemberships => Set<OrganizationMembership>();
|
||||
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>();
|
||||
@@ -41,9 +44,10 @@ public class AppDbContext(
|
||||
{
|
||||
base.OnModelCreating(builder);
|
||||
|
||||
builder.ConfigureOrganizationsModule();
|
||||
builder.ConfigureWorkspacesModule();
|
||||
builder.ConfigureClientsModule();
|
||||
builder.ConfigureProjectsModule();
|
||||
builder.ConfigureCampaignsModule();
|
||||
builder.ConfigureContentItemsModule();
|
||||
builder.ConfigureAssetsModule();
|
||||
builder.ConfigureCommentsModule();
|
||||
|
||||
@@ -10,7 +10,9 @@ 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.Organizations.Data;
|
||||
using Socialize.Api.Modules.Organizations.Services;
|
||||
using Socialize.Api.Modules.Workspaces.Data;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Options;
|
||||
@@ -19,11 +21,12 @@ namespace Socialize.Api.Infrastructure.Development;
|
||||
|
||||
public static class DevelopmentSeedExtensions
|
||||
{
|
||||
private static readonly Guid OrganizationId = Guid.Parse("99999999-9999-9999-9999-999999999999");
|
||||
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 +102,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(
|
||||
@@ -117,6 +120,12 @@ public static class DevelopmentSeedExtensions
|
||||
[
|
||||
]);
|
||||
|
||||
await EnsureOrganizationDataAsync(
|
||||
manager.Id,
|
||||
dev.Id,
|
||||
dbContext,
|
||||
cancellationToken);
|
||||
|
||||
await EnsureWorkspaceDataAsync(
|
||||
manager.Id,
|
||||
clientUser.Id,
|
||||
@@ -200,7 +209,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)
|
||||
@@ -224,6 +233,75 @@ public static class DevelopmentSeedExtensions
|
||||
return user;
|
||||
}
|
||||
|
||||
private static async Task EnsureOrganizationDataAsync(
|
||||
Guid managerUserId,
|
||||
Guid developerUserId,
|
||||
AppDbContext dbContext,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
Organization? organization = await dbContext.Organizations
|
||||
.SingleOrDefaultAsync(candidate => candidate.Id == OrganizationId, cancellationToken);
|
||||
if (organization is null)
|
||||
{
|
||||
organization = new Organization
|
||||
{
|
||||
Id = OrganizationId,
|
||||
Name = string.Empty,
|
||||
CreatedAt = DateTimeOffset.UtcNow,
|
||||
};
|
||||
dbContext.Organizations.Add(organization);
|
||||
}
|
||||
|
||||
organization.Name = "Northstar Collective";
|
||||
organization.OwnerUserId = managerUserId;
|
||||
|
||||
await UpsertOrganizationMembershipAsync(
|
||||
dbContext,
|
||||
Guid.Parse("99999999-9999-9999-9999-000000000001"),
|
||||
OrganizationId,
|
||||
managerUserId,
|
||||
OrganizationRoles.Owner,
|
||||
cancellationToken);
|
||||
|
||||
await UpsertOrganizationMembershipAsync(
|
||||
dbContext,
|
||||
Guid.Parse("99999999-9999-9999-9999-000000000002"),
|
||||
OrganizationId,
|
||||
developerUserId,
|
||||
OrganizationRoles.Admin,
|
||||
cancellationToken);
|
||||
|
||||
await dbContext.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
|
||||
private static async Task UpsertOrganizationMembershipAsync(
|
||||
AppDbContext dbContext,
|
||||
Guid membershipId,
|
||||
Guid organizationId,
|
||||
Guid userId,
|
||||
string role,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
OrganizationMembership? membership = await dbContext.OrganizationMemberships
|
||||
.SingleOrDefaultAsync(
|
||||
candidate => candidate.OrganizationId == organizationId && candidate.UserId == userId,
|
||||
cancellationToken);
|
||||
if (membership is null)
|
||||
{
|
||||
membership = new OrganizationMembership
|
||||
{
|
||||
Id = membershipId,
|
||||
OrganizationId = organizationId,
|
||||
UserId = userId,
|
||||
Role = role,
|
||||
CreatedAt = DateTimeOffset.UtcNow,
|
||||
};
|
||||
dbContext.OrganizationMemberships.Add(membership);
|
||||
}
|
||||
|
||||
membership.Role = role;
|
||||
}
|
||||
|
||||
private static async Task EnsureWorkspaceDataAsync(
|
||||
Guid managerUserId,
|
||||
Guid clientUserId,
|
||||
@@ -248,6 +326,7 @@ public static class DevelopmentSeedExtensions
|
||||
|
||||
workspace.Name = "Northstar Studio";
|
||||
workspace.Slug = "northstar-studio";
|
||||
workspace.OrganizationId = OrganizationId;
|
||||
workspace.OwnerUserId = managerUserId;
|
||||
workspace.TimeZone = "America/Montreal";
|
||||
await dbContext.SaveChangesAsync(cancellationToken);
|
||||
@@ -273,9 +352,9 @@ public static class DevelopmentSeedExtensions
|
||||
WorkspaceId,
|
||||
cancellationToken);
|
||||
|
||||
await UpsertProjectAsync(
|
||||
await UpsertCampaignAsync(
|
||||
dbContext,
|
||||
ScopedProjectId,
|
||||
ScopedCampaignId,
|
||||
WorkspaceId,
|
||||
ScopedClientId,
|
||||
"Spring Launch",
|
||||
@@ -285,9 +364,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 +382,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 +396,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 +570,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 +583,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 +611,7 @@ public static class DevelopmentSeedExtensions
|
||||
Guid id,
|
||||
Guid workspaceId,
|
||||
Guid clientId,
|
||||
Guid projectId,
|
||||
Guid campaignId,
|
||||
string title,
|
||||
string publicationMessage,
|
||||
string publicationTargets,
|
||||
@@ -559,7 +638,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;
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
using System.Security.Claims;
|
||||
using Socialize.Api.Modules.Identity.Contracts;
|
||||
using Socialize.Api.Modules.Organizations.Services;
|
||||
|
||||
namespace Socialize.Api.Infrastructure.Security;
|
||||
|
||||
public sealed class AccessScopeService
|
||||
public sealed class AccessScopeService(
|
||||
OrganizationAccessService organizationAccessService)
|
||||
{
|
||||
public bool IsManager(ClaimsPrincipal user)
|
||||
{
|
||||
@@ -36,21 +38,140 @@ 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);
|
||||
}
|
||||
|
||||
public Task<IReadOnlyCollection<Guid>> GetAccessibleWorkspaceIdsAsync(
|
||||
ClaimsPrincipal user,
|
||||
CancellationToken ct)
|
||||
{
|
||||
return organizationAccessService.GetAccessibleWorkspaceIdsAsync(user, ct);
|
||||
}
|
||||
|
||||
public async Task<bool> CanAccessWorkspaceAsync(
|
||||
ClaimsPrincipal user,
|
||||
Guid workspaceId,
|
||||
CancellationToken ct)
|
||||
{
|
||||
return CanAccessWorkspace(user, workspaceId)
|
||||
|| await organizationAccessService.HasInheritedWorkspacePermissionAsync(
|
||||
user,
|
||||
workspaceId,
|
||||
OrganizationPermissions.AccessOwnedWorkspaces,
|
||||
ct);
|
||||
}
|
||||
|
||||
public async Task<bool> CanManageWorkspaceAsync(
|
||||
ClaimsPrincipal user,
|
||||
Guid workspaceId,
|
||||
CancellationToken ct)
|
||||
{
|
||||
return IsManager(user)
|
||||
|| await organizationAccessService.HasInheritedWorkspacePermissionAsync(
|
||||
user,
|
||||
workspaceId,
|
||||
OrganizationPermissions.ManageWorkspaces,
|
||||
ct);
|
||||
}
|
||||
|
||||
public async Task<bool> CanCreateWorkspaceAsync(
|
||||
ClaimsPrincipal user,
|
||||
Guid organizationId,
|
||||
CancellationToken ct)
|
||||
{
|
||||
return IsManager(user)
|
||||
|| await organizationAccessService.HasOrganizationPermissionAsync(
|
||||
user,
|
||||
organizationId,
|
||||
OrganizationPermissions.CreateWorkspaces,
|
||||
ct);
|
||||
}
|
||||
|
||||
public async Task<bool> CanAccessClientAsync(
|
||||
ClaimsPrincipal user,
|
||||
Guid workspaceId,
|
||||
Guid clientId,
|
||||
CancellationToken ct)
|
||||
{
|
||||
if (IsManager(user) ||
|
||||
await organizationAccessService.HasInheritedWorkspacePermissionAsync(
|
||||
user,
|
||||
workspaceId,
|
||||
OrganizationPermissions.AccessOwnedWorkspaces,
|
||||
ct))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return user.GetWorkspaceScopeIds().Contains(workspaceId) && user.GetClientScopeIds().Contains(clientId);
|
||||
}
|
||||
|
||||
public async Task<bool> CanAccessCampaignAsync(
|
||||
ClaimsPrincipal user,
|
||||
Guid workspaceId,
|
||||
Guid clientId,
|
||||
Guid campaignId,
|
||||
CancellationToken ct)
|
||||
{
|
||||
if (IsManager(user) ||
|
||||
await organizationAccessService.HasInheritedWorkspacePermissionAsync(
|
||||
user,
|
||||
workspaceId,
|
||||
OrganizationPermissions.AccessOwnedWorkspaces,
|
||||
ct))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return await CanAccessClientAsync(user, workspaceId, clientId, ct) &&
|
||||
user.GetCampaignScopeIds().Contains(campaignId);
|
||||
}
|
||||
|
||||
public async Task<bool> CanContributeToCampaignAsync(
|
||||
ClaimsPrincipal user,
|
||||
Guid workspaceId,
|
||||
Guid clientId,
|
||||
Guid campaignId,
|
||||
CancellationToken ct)
|
||||
{
|
||||
return IsManager(user)
|
||||
|| await organizationAccessService.HasInheritedWorkspacePermissionAsync(
|
||||
user,
|
||||
workspaceId,
|
||||
OrganizationPermissions.ManageWorkspaces,
|
||||
ct)
|
||||
|| IsProvider(user) && await CanAccessCampaignAsync(user, workspaceId, clientId, campaignId, ct);
|
||||
}
|
||||
|
||||
public async Task<bool> CanReviewContentAsync(
|
||||
ClaimsPrincipal user,
|
||||
Guid workspaceId,
|
||||
Guid clientId,
|
||||
Guid campaignId,
|
||||
CancellationToken ct)
|
||||
{
|
||||
return IsManager(user)
|
||||
|| await organizationAccessService.HasInheritedWorkspacePermissionAsync(
|
||||
user,
|
||||
workspaceId,
|
||||
OrganizationPermissions.AccessOwnedWorkspaces,
|
||||
ct)
|
||||
|| IsProvider(user) && await CanAccessCampaignAsync(user, workspaceId, clientId, campaignId, ct)
|
||||
|| IsClient(user) && await CanAccessClientAsync(user, workspaceId, clientId, ct);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
1506
backend/src/Socialize.Api/Migrations/20260504195518_AddOrganizations.Designer.cs
generated
Normal file
1506
backend/src/Socialize.Api/Migrations/20260504195518_AddOrganizations.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,126 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Socialize.Api.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddOrganizations : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<Guid>(
|
||||
name: "OrganizationId",
|
||||
table: "Workspaces",
|
||||
type: "uuid",
|
||||
nullable: false,
|
||||
defaultValue: new Guid("00000000-0000-0000-0000-000000000000"));
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Organizations",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
Name = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
||||
OwnerUserId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
CreatedAt = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "CURRENT_TIMESTAMP")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Organizations", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "OrganizationMemberships",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
OrganizationId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
UserId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
Role = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false),
|
||||
CreatedAt = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "CURRENT_TIMESTAMP")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_OrganizationMemberships", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_OrganizationMemberships_Organizations_OrganizationId",
|
||||
column: x => x.OrganizationId,
|
||||
principalTable: "Organizations",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.Sql(
|
||||
"""
|
||||
INSERT INTO "Organizations" ("Id", "Name", "OwnerUserId", "CreatedAt")
|
||||
VALUES ('99999999-9999-9999-9999-999999999999', 'Northstar Collective', 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', CURRENT_TIMESTAMP);
|
||||
|
||||
UPDATE "Workspaces"
|
||||
SET "OrganizationId" = '99999999-9999-9999-9999-999999999999'
|
||||
WHERE "OrganizationId" = '00000000-0000-0000-0000-000000000000';
|
||||
|
||||
INSERT INTO "OrganizationMemberships" ("Id", "OrganizationId", "UserId", "Role", "CreatedAt")
|
||||
VALUES ('99999999-9999-9999-9999-000000000001', '99999999-9999-9999-9999-999999999999', 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', 'Owner', CURRENT_TIMESTAMP);
|
||||
""");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Workspaces_OrganizationId",
|
||||
table: "Workspaces",
|
||||
column: "OrganizationId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_OrganizationMemberships_OrganizationId",
|
||||
table: "OrganizationMemberships",
|
||||
column: "OrganizationId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_OrganizationMemberships_OrganizationId_UserId",
|
||||
table: "OrganizationMemberships",
|
||||
columns: new[] { "OrganizationId", "UserId" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_OrganizationMemberships_UserId",
|
||||
table: "OrganizationMemberships",
|
||||
column: "UserId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Organizations_OwnerUserId",
|
||||
table: "Organizations",
|
||||
column: "OwnerUserId");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_Workspaces_Organizations_OrganizationId",
|
||||
table: "Workspaces",
|
||||
column: "OrganizationId",
|
||||
principalTable: "Organizations",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_Workspaces_Organizations_OrganizationId",
|
||||
table: "Workspaces");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "OrganizationMemberships");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Organizations");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_Workspaces_OrganizationId",
|
||||
table: "Workspaces");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "OrganizationId",
|
||||
table: "Workspaces");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,57 +1203,64 @@ namespace Socialize.Api.Migrations
|
||||
b.ToTable("NotificationEvents", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Socialize.Api.Modules.Projects.Data.Project", b =>
|
||||
modelBuilder.Entity("Socialize.Api.Modules.Organizations.Data.Organization", 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")
|
||||
b.Property<Guid>("OwnerUserId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ClientId");
|
||||
b.HasIndex("OwnerUserId");
|
||||
|
||||
b.HasIndex("WorkspaceId");
|
||||
b.ToTable("Organizations", (string)null);
|
||||
});
|
||||
|
||||
b.HasIndex("ClientId", "Name")
|
||||
modelBuilder.Entity("Socialize.Api.Modules.Organizations.Data.OrganizationMembership", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.Property<Guid>("OrganizationId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Role")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("OrganizationId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.HasIndex("OrganizationId", "UserId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Projects", (string)null);
|
||||
b.ToTable("OrganizationMemberships", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Socialize.Api.Modules.Workspaces.Data.Workspace", b =>
|
||||
@@ -1235,6 +1295,9 @@ namespace Socialize.Api.Migrations
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<Guid>("OrganizationId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid>("OwnerUserId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
@@ -1260,6 +1323,8 @@ namespace Socialize.Api.Migrations
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("OrganizationId");
|
||||
|
||||
b.HasIndex("OwnerUserId");
|
||||
|
||||
b.HasIndex("Slug")
|
||||
@@ -1404,6 +1469,24 @@ namespace Socialize.Api.Migrations
|
||||
b.Navigation("FeedbackReport");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Socialize.Api.Modules.Organizations.Data.OrganizationMembership", b =>
|
||||
{
|
||||
b.HasOne("Socialize.Api.Modules.Organizations.Data.Organization", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("OrganizationId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Socialize.Api.Modules.Workspaces.Data.Workspace", b =>
|
||||
{
|
||||
b.HasOne("Socialize.Api.Modules.Organizations.Data.Organization", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("OrganizationId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Socialize.Api.Modules.Feedback.Data.FeedbackReport", b =>
|
||||
{
|
||||
b.Navigation("ActivityEntries");
|
||||
|
||||
@@ -1,139 +0,0 @@
|
||||
using FastEndpoints;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Security.Cryptography;
|
||||
using Socialize.Api.Data;
|
||||
using Socialize.Api.Infrastructure.Security;
|
||||
using Socialize.Api.Modules.Approvals.Data;
|
||||
using Socialize.Api.Modules.Approvals.Services;
|
||||
using Socialize.Api.Modules.Notifications.Contracts;
|
||||
using Socialize.Api.Modules.Workspaces.Data;
|
||||
|
||||
namespace Socialize.Api.Modules.Approvals.Handlers;
|
||||
|
||||
public record CreateApprovalRequestRequest(
|
||||
Guid WorkspaceId,
|
||||
Guid ContentItemId,
|
||||
string Stage,
|
||||
string ReviewerName,
|
||||
string ReviewerEmail,
|
||||
DateTimeOffset? DueAt);
|
||||
|
||||
public class CreateApprovalRequestRequestValidator
|
||||
: Validator<CreateApprovalRequestRequest>
|
||||
{
|
||||
public CreateApprovalRequestRequestValidator()
|
||||
{
|
||||
RuleFor(x => x.WorkspaceId).NotEmpty();
|
||||
RuleFor(x => x.ContentItemId).NotEmpty();
|
||||
RuleFor(x => x.Stage).NotEmpty().MaximumLength(64);
|
||||
RuleFor(x => x.ReviewerName).NotEmpty().MaximumLength(256);
|
||||
RuleFor(x => x.ReviewerEmail).NotEmpty().MaximumLength(256).EmailAddress();
|
||||
}
|
||||
}
|
||||
|
||||
public class CreateApprovalRequestHandler(
|
||||
AppDbContext dbContext,
|
||||
AccessScopeService accessScopeService,
|
||||
INotificationEventWriter notificationEventWriter)
|
||||
: Endpoint<CreateApprovalRequestRequest, ApprovalRequestDto>
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
Post("/api/approvals");
|
||||
Options(o => o.WithTags("Approvals"));
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(CreateApprovalRequestRequest request, CancellationToken ct)
|
||||
{
|
||||
var contentItem = await dbContext
|
||||
.ContentItems
|
||||
.SingleOrDefaultAsync(
|
||||
candidate => candidate.Id == request.ContentItemId && candidate.WorkspaceId == request.WorkspaceId,
|
||||
ct);
|
||||
|
||||
if (contentItem is null)
|
||||
{
|
||||
AddError(request => request.ContentItemId, "The selected content item does not exist in the active workspace.");
|
||||
await SendErrorsAsync(StatusCodes.Status400BadRequest, ct);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!accessScopeService.CanManageWorkspace(User, contentItem.WorkspaceId))
|
||||
{
|
||||
await SendForbiddenAsync(ct);
|
||||
return;
|
||||
}
|
||||
|
||||
Workspace? workspace = await dbContext.Workspaces.SingleOrDefaultAsync(candidate => candidate.Id == contentItem.WorkspaceId, ct);
|
||||
if (workspace is null)
|
||||
{
|
||||
await SendNotFoundAsync(ct);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ApprovalWorkflowRules.CanCreateSingleStepApprovalRequest(workspace.ApprovalMode))
|
||||
{
|
||||
AddError(request => request.WorkspaceId, workspace.ApprovalMode == ApprovalModes.None
|
||||
? "Approval workflow is disabled for this workspace."
|
||||
: "Move content to In approval to start the configured multi-level approval workflow.");
|
||||
await SendErrorsAsync(StatusCodes.Status400BadRequest, ct);
|
||||
return;
|
||||
}
|
||||
|
||||
var approval = new ApprovalRequest()
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
WorkspaceId = request.WorkspaceId,
|
||||
ContentItemId = request.ContentItemId,
|
||||
Stage = request.Stage.Trim(),
|
||||
ReviewerName = request.ReviewerName.Trim(),
|
||||
ReviewerEmail = request.ReviewerEmail.Trim(),
|
||||
RequestedByUserId = User.GetUserId(),
|
||||
DueAt = request.DueAt,
|
||||
State = "Pending",
|
||||
AccessToken = Convert.ToHexString(RandomNumberGenerator.GetBytes(16)).ToLowerInvariant(),
|
||||
SentAt = DateTimeOffset.UtcNow,
|
||||
};
|
||||
|
||||
dbContext.ApprovalRequests.Add(approval);
|
||||
|
||||
contentItem.Status = "In approval";
|
||||
|
||||
await dbContext.SaveChangesAsync(ct);
|
||||
|
||||
await notificationEventWriter.WriteAsync(
|
||||
new NotificationEventWriteModel(
|
||||
approval.WorkspaceId,
|
||||
approval.ContentItemId,
|
||||
"approval.requested",
|
||||
"ApprovalRequest",
|
||||
approval.Id,
|
||||
$"Approval requested from {approval.ReviewerName} for {contentItem.Title}.",
|
||||
null,
|
||||
approval.ReviewerEmail,
|
||||
$$"""{"stage":"{{approval.Stage}}","accessToken":"{{approval.AccessToken}}"}"""),
|
||||
ct);
|
||||
|
||||
ApprovalRequestDto dto = new(
|
||||
approval.Id,
|
||||
approval.WorkspaceId,
|
||||
approval.ContentItemId,
|
||||
approval.WorkflowInstanceId,
|
||||
approval.WorkflowStepSortOrder,
|
||||
approval.WorkflowStepTargetType,
|
||||
approval.WorkflowStepTargetValue,
|
||||
approval.WorkflowStepRequiredApproverCount,
|
||||
approval.Stage,
|
||||
approval.ReviewerName,
|
||||
approval.ReviewerEmail,
|
||||
approval.RequestedByUserId,
|
||||
approval.DueAt,
|
||||
approval.State,
|
||||
approval.AccessToken,
|
||||
approval.SentAt,
|
||||
approval.CompletedAt,
|
||||
[]);
|
||||
|
||||
await SendAsync(dto, StatusCodes.Status201Created, ct);
|
||||
}
|
||||
}
|
||||
@@ -61,7 +61,7 @@ public class GetApprovalsHandler(
|
||||
return;
|
||||
}
|
||||
|
||||
if (!accessScopeService.CanReviewContent(User, item.WorkspaceId, item.ClientId, item.ProjectId))
|
||||
if (!await accessScopeService.CanReviewContentAsync(User, item.WorkspaceId, item.ClientId, item.CampaignId, ct))
|
||||
{
|
||||
await SendForbiddenAsync(ct);
|
||||
return;
|
||||
|
||||
@@ -12,7 +12,6 @@ namespace Socialize.Api.Modules.Approvals.Handlers;
|
||||
|
||||
public record SubmitApprovalDecisionRequest(
|
||||
string Decision,
|
||||
string? Comment,
|
||||
string? ReviewerName,
|
||||
string? ReviewerEmail);
|
||||
|
||||
@@ -25,7 +24,6 @@ public class SubmitApprovalDecisionRequestValidator
|
||||
.NotEmpty()
|
||||
.Equal("Approved")
|
||||
.WithMessage("Only approved decisions are supported.");
|
||||
RuleFor(x => x.Comment).MaximumLength(2048);
|
||||
RuleFor(x => x.ReviewerName).MaximumLength(256);
|
||||
RuleFor(x => x.ReviewerEmail).MaximumLength(256).EmailAddress().When(x => !string.IsNullOrWhiteSpace(x.ReviewerEmail));
|
||||
}
|
||||
@@ -64,7 +62,7 @@ public class SubmitApprovalDecisionHandler(
|
||||
}
|
||||
|
||||
if (User?.Identity?.IsAuthenticated == true &&
|
||||
!accessScopeService.CanReviewContent(User, contentItem.WorkspaceId, contentItem.ClientId, contentItem.ProjectId))
|
||||
!await accessScopeService.CanReviewContentAsync(User, contentItem.WorkspaceId, contentItem.ClientId, contentItem.CampaignId, ct))
|
||||
{
|
||||
await SendForbiddenAsync(ct);
|
||||
return;
|
||||
@@ -90,7 +88,7 @@ public class SubmitApprovalDecisionHandler(
|
||||
Id = Guid.NewGuid(),
|
||||
ApprovalRequestId = approval.Id,
|
||||
Decision = normalizedDecision,
|
||||
Comment = string.IsNullOrWhiteSpace(request.Comment) ? null : request.Comment.Trim(),
|
||||
Comment = null,
|
||||
DecidedByUserId = User?.Identity?.IsAuthenticated == true ? User.GetUserId() : null,
|
||||
DecidedByName = decidedByName,
|
||||
DecidedByEmail = decidedByEmail,
|
||||
|
||||
@@ -12,11 +12,6 @@ public static class ApprovalModes
|
||||
|
||||
public static class ApprovalWorkflowRules
|
||||
{
|
||||
public static bool CanCreateSingleStepApprovalRequest(string approvalMode)
|
||||
{
|
||||
return approvalMode is ApprovalModes.Optional or ApprovalModes.Required;
|
||||
}
|
||||
|
||||
public static bool BlocksManualApprovedOrScheduledStatus(string approvalMode)
|
||||
{
|
||||
return approvalMode is ApprovalModes.Required or ApprovalModes.MultiLevel;
|
||||
|
||||
@@ -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))
|
||||
!await accessScopeService.CanContributeToCampaignAsync(User, contentItem.WorkspaceId, contentItem.ClientId, contentItem.CampaignId, ct))
|
||||
{
|
||||
await SendForbiddenAsync(ct);
|
||||
return;
|
||||
|
||||
@@ -58,7 +58,7 @@ public class CreateGoogleDriveAssetHandler(
|
||||
return;
|
||||
}
|
||||
|
||||
if (!accessScopeService.CanContributeToProject(User, contentItem.WorkspaceId, contentItem.ClientId, contentItem.ProjectId))
|
||||
if (!await accessScopeService.CanContributeToCampaignAsync(User, contentItem.WorkspaceId, contentItem.ClientId, contentItem.CampaignId, ct))
|
||||
{
|
||||
await SendForbiddenAsync(ct);
|
||||
return;
|
||||
|
||||
@@ -52,7 +52,7 @@ public class GetAssetsHandler(
|
||||
return;
|
||||
}
|
||||
|
||||
if (!accessScopeService.CanReviewContent(User, item.WorkspaceId, item.ClientId, item.ProjectId))
|
||||
if (!await accessScopeService.CanReviewContentAsync(User, item.WorkspaceId, item.ClientId, item.CampaignId, ct))
|
||||
{
|
||||
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,20 +32,20 @@ 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))
|
||||
if (!await accessScopeService.CanManageWorkspaceAsync(User, request.WorkspaceId, ct))
|
||||
{
|
||||
await SendForbiddenAsync(ct);
|
||||
return;
|
||||
@@ -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,82 @@
|
||||
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))
|
||||
{
|
||||
IReadOnlyCollection<Guid> workspaceScopeIds = await accessScopeService.GetAccessibleWorkspaceIdsAsync(User, ct);
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -47,7 +47,7 @@ public class ChangeClientPortraitHandler(
|
||||
return;
|
||||
}
|
||||
|
||||
if (!accessScopeService.CanManageWorkspace(User, client.WorkspaceId))
|
||||
if (!await accessScopeService.CanManageWorkspaceAsync(User, client.WorkspaceId, ct))
|
||||
{
|
||||
await SendForbiddenAsync(ct);
|
||||
return;
|
||||
|
||||
@@ -41,7 +41,7 @@ public class CreateClientHandler(
|
||||
|
||||
public override async Task HandleAsync(CreateClientRequest request, CancellationToken ct)
|
||||
{
|
||||
if (!accessScopeService.CanManageWorkspace(User, request.WorkspaceId))
|
||||
if (!await accessScopeService.CanManageWorkspaceAsync(User, request.WorkspaceId, ct))
|
||||
{
|
||||
await SendForbiddenAsync(ct);
|
||||
return;
|
||||
|
||||
@@ -33,16 +33,9 @@ public class GetClientsHandler(
|
||||
{
|
||||
IQueryable<Client> query = dbContext.Clients.AsQueryable();
|
||||
|
||||
if (accessScopeService.IsManager(User))
|
||||
if (!accessScopeService.IsManager(User))
|
||||
{
|
||||
if (request.WorkspaceId.HasValue)
|
||||
{
|
||||
query = query.Where(client => client.WorkspaceId == request.WorkspaceId.Value);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
IReadOnlyCollection<Guid> workspaceScopeIds = User.GetWorkspaceScopeIds();
|
||||
IReadOnlyCollection<Guid> workspaceScopeIds = await accessScopeService.GetAccessibleWorkspaceIdsAsync(User, ct);
|
||||
IReadOnlyCollection<Guid> clientScopeIds = User.GetClientScopeIds();
|
||||
|
||||
query = query.Where(client => workspaceScopeIds.Contains(client.WorkspaceId));
|
||||
@@ -52,11 +45,12 @@ public class GetClientsHandler(
|
||||
query = query.Where(client => clientScopeIds.Contains(client.Id));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (request.WorkspaceId.HasValue)
|
||||
{
|
||||
query = query.Where(client => client.WorkspaceId == request.WorkspaceId.Value);
|
||||
}
|
||||
}
|
||||
|
||||
List<ClientDto> clients = await query
|
||||
.OrderBy(client => client.Name)
|
||||
|
||||
@@ -50,7 +50,7 @@ public class UpdateClientHandler(
|
||||
return;
|
||||
}
|
||||
|
||||
if (!accessScopeService.CanManageWorkspace(User, client.WorkspaceId))
|
||||
if (!await accessScopeService.CanManageWorkspaceAsync(User, client.WorkspaceId, ct))
|
||||
{
|
||||
await SendForbiddenAsync(ct);
|
||||
return;
|
||||
|
||||
@@ -51,7 +51,7 @@ public class CreateCommentHandler(
|
||||
return;
|
||||
}
|
||||
|
||||
if (!accessScopeService.CanReviewContent(User, contentItem.WorkspaceId, contentItem.ClientId, contentItem.ProjectId))
|
||||
if (!await accessScopeService.CanReviewContentAsync(User, contentItem.WorkspaceId, contentItem.ClientId, contentItem.CampaignId, ct))
|
||||
{
|
||||
await SendForbiddenAsync(ct);
|
||||
return;
|
||||
|
||||
@@ -44,7 +44,7 @@ public class GetCommentsHandler(
|
||||
return;
|
||||
}
|
||||
|
||||
if (!accessScopeService.CanReviewContent(User, item.WorkspaceId, item.ClientId, item.ProjectId))
|
||||
if (!await accessScopeService.CanReviewContentAsync(User, item.WorkspaceId, item.ClientId, item.CampaignId, ct))
|
||||
{
|
||||
await SendForbiddenAsync(ct);
|
||||
return;
|
||||
|
||||
@@ -39,8 +39,8 @@ public class ResolveCommentHandler(
|
||||
return;
|
||||
}
|
||||
|
||||
bool canResolve = accessScopeService.CanManageWorkspace(User, comment.WorkspaceId)
|
||||
|| accessScopeService.CanContributeToProject(User, contentItem.WorkspaceId, contentItem.ClientId, contentItem.ProjectId);
|
||||
bool canResolve = await accessScopeService.CanManageWorkspaceAsync(User, comment.WorkspaceId, ct)
|
||||
|| await accessScopeService.CanContributeToCampaignAsync(User, contentItem.WorkspaceId, contentItem.ClientId, contentItem.CampaignId, ct);
|
||||
|
||||
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 (!await accessScopeService.CanContributeToCampaignAsync(User, request.WorkspaceId, request.ClientId, request.CampaignId, ct))
|
||||
{
|
||||
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 (!await accessScopeService.CanContributeToCampaignAsync(User, item.WorkspaceId, item.ClientId, item.CampaignId, ct))
|
||||
{
|
||||
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 (!await accessScopeService.CanReviewContentAsync(User, item.WorkspaceId, item.ClientId, item.CampaignId, ct))
|
||||
{
|
||||
await SendForbiddenAsync(ct);
|
||||
return;
|
||||
|
||||
@@ -41,7 +41,7 @@ public class GetContentItemRevisionsHandler(
|
||||
return;
|
||||
}
|
||||
|
||||
if (!accessScopeService.CanReviewContent(User, item.WorkspaceId, item.ClientId, item.ProjectId))
|
||||
if (!await accessScopeService.CanReviewContentAsync(User, item.WorkspaceId, item.ClientId, item.CampaignId, ct))
|
||||
{
|
||||
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,
|
||||
@@ -39,9 +39,9 @@ public class GetContentItemsHandler(
|
||||
|
||||
if (!accessScopeService.IsManager(User))
|
||||
{
|
||||
IReadOnlyCollection<Guid> workspaceScopeIds = User.GetWorkspaceScopeIds();
|
||||
IReadOnlyCollection<Guid> workspaceScopeIds = await accessScopeService.GetAccessibleWorkspaceIdsAsync(User, ct);
|
||||
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,
|
||||
|
||||
@@ -54,7 +54,7 @@ public class UpdateContentItemStatusHandler(
|
||||
return;
|
||||
}
|
||||
|
||||
if (!accessScopeService.CanManageWorkspace(User, item.WorkspaceId))
|
||||
if (!await accessScopeService.CanManageWorkspaceAsync(User, item.WorkspaceId, ct))
|
||||
{
|
||||
await SendForbiddenAsync(ct);
|
||||
return;
|
||||
@@ -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 (!await accessScopeService.CanReviewContentAsync(User, item.WorkspaceId, item.ClientId, item.CampaignId, ct))
|
||||
{
|
||||
await SendForbiddenAsync(ct);
|
||||
return;
|
||||
@@ -58,7 +58,7 @@ public class GetNotificationsHandler(
|
||||
|
||||
if (!accessScopeService.IsManager(User))
|
||||
{
|
||||
IReadOnlyCollection<Guid> workspaceScopeIds = User.GetWorkspaceScopeIds();
|
||||
IReadOnlyCollection<Guid> workspaceScopeIds = await accessScopeService.GetAccessibleWorkspaceIdsAsync(User, ct);
|
||||
query = query.Where(notificationEvent =>
|
||||
workspaceScopeIds.Contains(notificationEvent.WorkspaceId) ||
|
||||
notificationEvent.RecipientUserId == currentUserId);
|
||||
|
||||
@@ -30,7 +30,7 @@ public class MarkNotificationAsReadHandler(
|
||||
|
||||
Guid currentUserId = User.GetUserId();
|
||||
bool canReadRecipientNotification = notificationEvent.RecipientUserId == currentUserId;
|
||||
if (!canReadRecipientNotification && !accessScopeService.CanAccessWorkspace(User, notificationEvent.WorkspaceId))
|
||||
if (!canReadRecipientNotification && !await accessScopeService.CanAccessWorkspaceAsync(User, notificationEvent.WorkspaceId, ct))
|
||||
{
|
||||
await SendForbiddenAsync(ct);
|
||||
return;
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace Socialize.Api.Modules.Organizations.Data;
|
||||
|
||||
public class Organization
|
||||
{
|
||||
public Guid Id { get; init; }
|
||||
public required string Name { get; set; }
|
||||
public Guid OwnerUserId { get; set; }
|
||||
public DateTimeOffset CreatedAt { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace Socialize.Api.Modules.Organizations.Data;
|
||||
|
||||
public class OrganizationMembership
|
||||
{
|
||||
public Guid Id { get; init; }
|
||||
public Guid OrganizationId { get; set; }
|
||||
public Guid UserId { get; set; }
|
||||
public required string Role { get; set; }
|
||||
public DateTimeOffset CreatedAt { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Socialize.Api.Modules.Organizations.Data;
|
||||
|
||||
public static class OrganizationModelConfiguration
|
||||
{
|
||||
public static ModelBuilder ConfigureOrganizationsModule(this ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.Entity<Organization>(organization =>
|
||||
{
|
||||
organization.ToTable("Organizations");
|
||||
organization.HasKey(x => x.Id);
|
||||
organization.Property(x => x.Name).HasMaxLength(256).IsRequired();
|
||||
organization.Property(x => x.CreatedAt)
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
organization.HasIndex(x => x.OwnerUserId);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<OrganizationMembership>(membership =>
|
||||
{
|
||||
membership.ToTable("OrganizationMemberships");
|
||||
membership.HasKey(x => x.Id);
|
||||
membership.Property(x => x.Role).HasMaxLength(64).IsRequired();
|
||||
membership.Property(x => x.CreatedAt)
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
membership.HasIndex(x => x.OrganizationId);
|
||||
membership.HasIndex(x => x.UserId);
|
||||
membership.HasIndex(x => new { x.OrganizationId, x.UserId }).IsUnique();
|
||||
membership.HasOne<Organization>()
|
||||
.WithMany()
|
||||
.HasForeignKey(x => x.OrganizationId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
return modelBuilder;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using Socialize.Api.Modules.Organizations.Services;
|
||||
|
||||
namespace Socialize.Api.Modules.Organizations;
|
||||
|
||||
public static class DependencyInjection
|
||||
{
|
||||
public static WebApplicationBuilder AddOrganizationsModule(
|
||||
this WebApplicationBuilder builder)
|
||||
{
|
||||
builder.Services.AddScoped<OrganizationAccessService>();
|
||||
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
using FastEndpoints;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Socialize.Api.Data;
|
||||
using Socialize.Api.Modules.Identity.Data;
|
||||
using Socialize.Api.Modules.Organizations.Data;
|
||||
using Socialize.Api.Modules.Organizations.Services;
|
||||
using Socialize.Api.Modules.Workspaces.Handlers;
|
||||
|
||||
namespace Socialize.Api.Modules.Organizations.Handlers;
|
||||
|
||||
public class GetOrganizationHandler(
|
||||
AppDbContext dbContext,
|
||||
OrganizationAccessService organizationAccessService)
|
||||
: EndpointWithoutRequest<OrganizationDto>
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
Get("/api/organizations/{organizationId:guid}");
|
||||
Options(o => o.WithTags("Organizations"));
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(CancellationToken ct)
|
||||
{
|
||||
Guid organizationId = Route<Guid>("organizationId");
|
||||
|
||||
Organization? organization = await dbContext.Organizations
|
||||
.SingleOrDefaultAsync(candidate => candidate.Id == organizationId, ct);
|
||||
if (organization is null)
|
||||
{
|
||||
await SendNotFoundAsync(ct);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!await organizationAccessService.CanAccessOrganizationAsync(User, organizationId, ct))
|
||||
{
|
||||
await SendForbiddenAsync(ct);
|
||||
return;
|
||||
}
|
||||
|
||||
IReadOnlyCollection<string> currentUserPermissions = await organizationAccessService.GetUserOrganizationPermissionsAsync(
|
||||
User,
|
||||
organizationId,
|
||||
ct);
|
||||
|
||||
IReadOnlyCollection<OrganizationMemberDto> members = await GetMembersAsync(organizationId, ct);
|
||||
IReadOnlyCollection<WorkspaceDto> workspaces = await GetWorkspacesAsync(organizationId, ct);
|
||||
|
||||
await SendOkAsync(
|
||||
OrganizationDto.FromOrganization(
|
||||
organization,
|
||||
currentUserPermissions,
|
||||
members,
|
||||
workspaces),
|
||||
ct);
|
||||
}
|
||||
|
||||
private async Task<IReadOnlyCollection<OrganizationMemberDto>> GetMembersAsync(
|
||||
Guid organizationId,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var rows = await dbContext.OrganizationMemberships
|
||||
.Where(membership => membership.OrganizationId == organizationId)
|
||||
.Join(
|
||||
dbContext.Users,
|
||||
membership => membership.UserId,
|
||||
user => user.Id,
|
||||
(membership, user) => new { Membership = membership, User = user })
|
||||
.OrderBy(row => row.User.Lastname)
|
||||
.ThenBy(row => row.User.Firstname)
|
||||
.ThenBy(row => row.User.Email)
|
||||
.ToListAsync(ct);
|
||||
|
||||
return rows
|
||||
.Select(row => new OrganizationMemberDto(
|
||||
row.User.Id,
|
||||
BuildDisplayName(row.User),
|
||||
row.User.Email ?? string.Empty,
|
||||
row.User.PortraitUrl,
|
||||
row.Membership.Role,
|
||||
OrganizationPermissionRules.GetPermissionsForRole(row.Membership.Role),
|
||||
row.Membership.CreatedAt))
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
private async Task<IReadOnlyCollection<WorkspaceDto>> GetWorkspacesAsync(
|
||||
Guid organizationId,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var workspaces = await dbContext.Workspaces
|
||||
.Where(workspace => workspace.OrganizationId == organizationId)
|
||||
.OrderBy(workspace => workspace.Name)
|
||||
.ToListAsync(ct);
|
||||
|
||||
return workspaces
|
||||
.Select(workspace => WorkspaceDto.FromWorkspace(workspace, []))
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
private static string BuildDisplayName(User user)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(user.Alias))
|
||||
{
|
||||
return user.Alias;
|
||||
}
|
||||
|
||||
string fullName = $"{user.Firstname} {user.Lastname}".Trim();
|
||||
if (!string.IsNullOrWhiteSpace(fullName))
|
||||
{
|
||||
return fullName;
|
||||
}
|
||||
|
||||
return user.Email ?? user.UserName ?? user.Id.ToString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using FastEndpoints;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Socialize.Api.Data;
|
||||
using Socialize.Api.Modules.Organizations.Data;
|
||||
using Socialize.Api.Modules.Organizations.Services;
|
||||
|
||||
namespace Socialize.Api.Modules.Organizations.Handlers;
|
||||
|
||||
public class GetOrganizationsHandler(
|
||||
AppDbContext dbContext,
|
||||
OrganizationAccessService organizationAccessService)
|
||||
: EndpointWithoutRequest<IReadOnlyCollection<OrganizationDto>>
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
Get("/api/organizations");
|
||||
Options(o => o.WithTags("Organizations"));
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(CancellationToken ct)
|
||||
{
|
||||
IReadOnlyCollection<Guid> organizationIds = await organizationAccessService.GetAccessibleOrganizationIdsAsync(User, ct);
|
||||
|
||||
List<Organization> organizations = await dbContext.Organizations
|
||||
.Where(organization => organizationIds.Contains(organization.Id))
|
||||
.OrderBy(organization => organization.Name)
|
||||
.ToListAsync(ct);
|
||||
|
||||
List<OrganizationDto> response = [];
|
||||
foreach (Organization organization in organizations)
|
||||
{
|
||||
IReadOnlyCollection<string> permissions = await organizationAccessService.GetUserOrganizationPermissionsAsync(
|
||||
User,
|
||||
organization.Id,
|
||||
ct);
|
||||
response.Add(OrganizationDto.FromOrganization(organization, permissions));
|
||||
}
|
||||
|
||||
await SendOkAsync(response, ct);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using Socialize.Api.Modules.Organizations.Data;
|
||||
using Socialize.Api.Modules.Workspaces.Handlers;
|
||||
|
||||
namespace Socialize.Api.Modules.Organizations.Handlers;
|
||||
|
||||
public record OrganizationMemberDto(
|
||||
Guid UserId,
|
||||
string DisplayName,
|
||||
string Email,
|
||||
string? PortraitUrl,
|
||||
string Role,
|
||||
IReadOnlyCollection<string> Permissions,
|
||||
DateTimeOffset CreatedAt);
|
||||
|
||||
public record OrganizationDto(
|
||||
Guid Id,
|
||||
string Name,
|
||||
Guid OwnerUserId,
|
||||
IReadOnlyCollection<string> CurrentUserPermissions,
|
||||
IReadOnlyCollection<OrganizationMemberDto> Members,
|
||||
IReadOnlyCollection<WorkspaceDto> Workspaces,
|
||||
DateTimeOffset CreatedAt)
|
||||
{
|
||||
public static OrganizationDto FromOrganization(
|
||||
Organization organization,
|
||||
IReadOnlyCollection<string> currentUserPermissions,
|
||||
IReadOnlyCollection<OrganizationMemberDto>? members = null,
|
||||
IReadOnlyCollection<WorkspaceDto>? workspaces = null)
|
||||
{
|
||||
return new OrganizationDto(
|
||||
organization.Id,
|
||||
organization.Name,
|
||||
organization.OwnerUserId,
|
||||
currentUserPermissions,
|
||||
members ?? [],
|
||||
workspaces ?? [],
|
||||
organization.CreatedAt);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
using System.Security.Claims;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Socialize.Api.Data;
|
||||
using Socialize.Api.Infrastructure.Security;
|
||||
using Socialize.Api.Modules.Identity.Contracts;
|
||||
|
||||
namespace Socialize.Api.Modules.Organizations.Services;
|
||||
|
||||
public sealed class OrganizationAccessService(
|
||||
AppDbContext dbContext)
|
||||
{
|
||||
public bool IsGlobalManager(ClaimsPrincipal user)
|
||||
{
|
||||
return user.IsInRole(KnownRoles.Administrator) || user.IsInRole(KnownRoles.Manager);
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyCollection<Guid>> GetAccessibleOrganizationIdsAsync(
|
||||
ClaimsPrincipal user,
|
||||
CancellationToken ct)
|
||||
{
|
||||
if (IsGlobalManager(user))
|
||||
{
|
||||
return await dbContext.Organizations
|
||||
.Select(organization => organization.Id)
|
||||
.ToArrayAsync(ct);
|
||||
}
|
||||
|
||||
Guid userId = user.GetUserId();
|
||||
|
||||
Guid[] ownedOrganizationIds = await dbContext.Organizations
|
||||
.Where(organization => organization.OwnerUserId == userId)
|
||||
.Select(organization => organization.Id)
|
||||
.ToArrayAsync(ct);
|
||||
|
||||
Guid[] memberOrganizationIds = await dbContext.OrganizationMemberships
|
||||
.Where(membership => membership.UserId == userId)
|
||||
.Select(membership => membership.OrganizationId)
|
||||
.ToArrayAsync(ct);
|
||||
|
||||
return ownedOrganizationIds
|
||||
.Concat(memberOrganizationIds)
|
||||
.Distinct()
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyCollection<Guid>> GetAccessibleWorkspaceIdsAsync(
|
||||
ClaimsPrincipal user,
|
||||
CancellationToken ct)
|
||||
{
|
||||
if (IsGlobalManager(user))
|
||||
{
|
||||
return await dbContext.Workspaces
|
||||
.Select(workspace => workspace.Id)
|
||||
.ToArrayAsync(ct);
|
||||
}
|
||||
|
||||
Guid[] directWorkspaceIds = user.GetWorkspaceScopeIds().ToArray();
|
||||
Guid[] organizationWorkspaceIds = await GetInheritedWorkspaceIdsAsync(user, OrganizationPermissions.AccessOwnedWorkspaces, ct);
|
||||
|
||||
return directWorkspaceIds
|
||||
.Concat(organizationWorkspaceIds)
|
||||
.Distinct()
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
public async Task<bool> CanAccessOrganizationAsync(
|
||||
ClaimsPrincipal user,
|
||||
Guid organizationId,
|
||||
CancellationToken ct)
|
||||
{
|
||||
if (IsGlobalManager(user))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
Guid userId = user.GetUserId();
|
||||
|
||||
return await dbContext.Organizations.AnyAsync(
|
||||
organization => organization.Id == organizationId && organization.OwnerUserId == userId,
|
||||
ct)
|
||||
|| await dbContext.OrganizationMemberships.AnyAsync(
|
||||
membership => membership.OrganizationId == organizationId && membership.UserId == userId,
|
||||
ct);
|
||||
}
|
||||
|
||||
public async Task<bool> HasOrganizationPermissionAsync(
|
||||
ClaimsPrincipal user,
|
||||
Guid organizationId,
|
||||
string permission,
|
||||
CancellationToken ct)
|
||||
{
|
||||
if (IsGlobalManager(user))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
Guid userId = user.GetUserId();
|
||||
|
||||
bool owner = await dbContext.Organizations.AnyAsync(
|
||||
organization => organization.Id == organizationId && organization.OwnerUserId == userId,
|
||||
ct);
|
||||
if (owner)
|
||||
{
|
||||
return OrganizationPermissionRules.RoleHasPermission(OrganizationRoles.Owner, permission);
|
||||
}
|
||||
|
||||
string[] roles = await dbContext.OrganizationMemberships
|
||||
.Where(membership => membership.OrganizationId == organizationId && membership.UserId == userId)
|
||||
.Select(membership => membership.Role)
|
||||
.ToArrayAsync(ct);
|
||||
|
||||
return roles.Any(role => OrganizationPermissionRules.RoleHasPermission(role, permission));
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyCollection<string>> GetUserOrganizationPermissionsAsync(
|
||||
ClaimsPrincipal user,
|
||||
Guid organizationId,
|
||||
CancellationToken ct)
|
||||
{
|
||||
if (IsGlobalManager(user))
|
||||
{
|
||||
return OrganizationPermissionRules.GetPermissionsForRole(OrganizationRoles.Owner);
|
||||
}
|
||||
|
||||
Guid userId = user.GetUserId();
|
||||
|
||||
bool owner = await dbContext.Organizations.AnyAsync(
|
||||
organization => organization.Id == organizationId && organization.OwnerUserId == userId,
|
||||
ct);
|
||||
if (owner)
|
||||
{
|
||||
return OrganizationPermissionRules.GetPermissionsForRole(OrganizationRoles.Owner);
|
||||
}
|
||||
|
||||
string[] roles = await dbContext.OrganizationMemberships
|
||||
.Where(membership => membership.OrganizationId == organizationId && membership.UserId == userId)
|
||||
.Select(membership => membership.Role)
|
||||
.ToArrayAsync(ct);
|
||||
|
||||
return roles
|
||||
.SelectMany(OrganizationPermissionRules.GetPermissionsForRole)
|
||||
.Distinct(StringComparer.Ordinal)
|
||||
.OrderBy(permission => permission)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
public async Task<bool> HasInheritedWorkspacePermissionAsync(
|
||||
ClaimsPrincipal user,
|
||||
Guid workspaceId,
|
||||
string permission,
|
||||
CancellationToken ct)
|
||||
{
|
||||
if (IsGlobalManager(user))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
Guid? organizationId = await dbContext.Workspaces
|
||||
.Where(workspace => workspace.Id == workspaceId)
|
||||
.Select(workspace => (Guid?)workspace.OrganizationId)
|
||||
.SingleOrDefaultAsync(ct);
|
||||
|
||||
return organizationId.HasValue &&
|
||||
await HasOrganizationPermissionAsync(user, organizationId.Value, permission, ct);
|
||||
}
|
||||
|
||||
private async Task<Guid[]> GetInheritedWorkspaceIdsAsync(
|
||||
ClaimsPrincipal user,
|
||||
string permission,
|
||||
CancellationToken ct)
|
||||
{
|
||||
Guid userId = user.GetUserId();
|
||||
|
||||
Guid[] ownedOrganizationIds = await dbContext.Organizations
|
||||
.Where(organization => organization.OwnerUserId == userId)
|
||||
.Select(organization => organization.Id)
|
||||
.ToArrayAsync(ct);
|
||||
|
||||
List<Data.OrganizationMembership> memberships = await dbContext.OrganizationMemberships
|
||||
.Where(membership => membership.UserId == userId)
|
||||
.ToListAsync(ct);
|
||||
Guid[] memberOrganizationIds = memberships
|
||||
.Where(membership => OrganizationPermissionRules.RoleHasPermission(membership.Role, permission))
|
||||
.Select(membership => membership.OrganizationId)
|
||||
.ToArray();
|
||||
|
||||
Guid[] organizationIds = ownedOrganizationIds
|
||||
.Concat(memberOrganizationIds)
|
||||
.Distinct()
|
||||
.ToArray();
|
||||
|
||||
if (organizationIds.Length == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
return await dbContext.Workspaces
|
||||
.Where(workspace => organizationIds.Contains(workspace.OrganizationId))
|
||||
.Select(workspace => workspace.Id)
|
||||
.ToArrayAsync(ct);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
namespace Socialize.Api.Modules.Organizations.Services;
|
||||
|
||||
public static class OrganizationPermissionRules
|
||||
{
|
||||
public static IReadOnlyCollection<string> GetPermissionsForRole(string role)
|
||||
{
|
||||
return role switch
|
||||
{
|
||||
OrganizationRoles.Owner =>
|
||||
[
|
||||
OrganizationPermissions.ManageOrganizationSettings,
|
||||
OrganizationPermissions.ManageOrganizationMembers,
|
||||
OrganizationPermissions.CreateWorkspaces,
|
||||
OrganizationPermissions.ManageWorkspaces,
|
||||
OrganizationPermissions.ManageBilling,
|
||||
OrganizationPermissions.ManageConnectors,
|
||||
OrganizationPermissions.AccessOwnedWorkspaces,
|
||||
],
|
||||
OrganizationRoles.Admin =>
|
||||
[
|
||||
OrganizationPermissions.ManageOrganizationSettings,
|
||||
OrganizationPermissions.ManageOrganizationMembers,
|
||||
OrganizationPermissions.CreateWorkspaces,
|
||||
OrganizationPermissions.ManageWorkspaces,
|
||||
OrganizationPermissions.ManageConnectors,
|
||||
OrganizationPermissions.AccessOwnedWorkspaces,
|
||||
],
|
||||
OrganizationRoles.BillingManager =>
|
||||
[
|
||||
OrganizationPermissions.ManageBilling,
|
||||
OrganizationPermissions.AccessOwnedWorkspaces,
|
||||
],
|
||||
OrganizationRoles.ConnectorManager =>
|
||||
[
|
||||
OrganizationPermissions.ManageConnectors,
|
||||
OrganizationPermissions.AccessOwnedWorkspaces,
|
||||
],
|
||||
OrganizationRoles.Member =>
|
||||
[
|
||||
OrganizationPermissions.AccessOwnedWorkspaces,
|
||||
],
|
||||
_ => [],
|
||||
};
|
||||
}
|
||||
|
||||
public static bool RoleHasPermission(string role, string permission)
|
||||
{
|
||||
return GetPermissionsForRole(role).Contains(permission, StringComparer.Ordinal);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace Socialize.Api.Modules.Organizations.Services;
|
||||
|
||||
public static class OrganizationPermissions
|
||||
{
|
||||
public const string ManageOrganizationSettings = "ManageOrganizationSettings";
|
||||
public const string ManageOrganizationMembers = "ManageOrganizationMembers";
|
||||
public const string CreateWorkspaces = "CreateWorkspaces";
|
||||
public const string ManageWorkspaces = "ManageWorkspaces";
|
||||
public const string ManageBilling = "ManageBilling";
|
||||
public const string ManageConnectors = "ManageConnectors";
|
||||
public const string AccessOwnedWorkspaces = "AccessOwnedWorkspaces";
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace Socialize.Api.Modules.Organizations.Services;
|
||||
|
||||
public static class OrganizationRoles
|
||||
{
|
||||
public const string Owner = "Owner";
|
||||
public const string Admin = "Admin";
|
||||
public const string BillingManager = "BillingManager";
|
||||
public const string ConnectorManager = "ConnectorManager";
|
||||
public const string Member = "Member";
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ public class Workspace
|
||||
public required string Name { get; set; }
|
||||
public required string Slug { get; set; }
|
||||
public string? LogoUrl { get; set; }
|
||||
public Guid OrganizationId { get; set; }
|
||||
public Guid OwnerUserId { get; set; }
|
||||
public required string TimeZone { get; set; }
|
||||
public string ApprovalMode { get; set; } = "Required";
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Socialize.Api.Modules.Organizations.Data;
|
||||
|
||||
namespace Socialize.Api.Modules.Workspaces.Data;
|
||||
|
||||
@@ -22,7 +23,12 @@ public static class WorkspaceModelConfiguration
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
workspace.HasIndex(x => x.Slug).IsUnique();
|
||||
workspace.HasIndex(x => x.OrganizationId);
|
||||
workspace.HasIndex(x => x.OwnerUserId);
|
||||
workspace.HasOne<Organization>()
|
||||
.WithMany()
|
||||
.HasForeignKey(x => x.OrganizationId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<WorkspaceInvite>(workspaceInvite =>
|
||||
|
||||
@@ -47,7 +47,7 @@ public class ChangeWorkspaceLogoHandler(
|
||||
return;
|
||||
}
|
||||
|
||||
if (!accessScopeService.CanManageWorkspace(User, workspace.Id))
|
||||
if (!await accessScopeService.CanManageWorkspaceAsync(User, workspace.Id, ct))
|
||||
{
|
||||
await SendForbiddenAsync(ct);
|
||||
return;
|
||||
|
||||
@@ -7,6 +7,7 @@ using Socialize.Api.Modules.Workspaces.Data;
|
||||
namespace Socialize.Api.Modules.Workspaces.Handlers;
|
||||
|
||||
public record CreateWorkspaceRequest(
|
||||
Guid OrganizationId,
|
||||
string Name,
|
||||
string Slug,
|
||||
string TimeZone);
|
||||
@@ -16,6 +17,7 @@ public class CreateWorkspaceRequestValidator
|
||||
{
|
||||
public CreateWorkspaceRequestValidator()
|
||||
{
|
||||
RuleFor(x => x.OrganizationId).NotEmpty();
|
||||
RuleFor(x => x.Name).NotEmpty().MaximumLength(256);
|
||||
RuleFor(x => x.Slug)
|
||||
.NotEmpty()
|
||||
@@ -38,12 +40,21 @@ public class CreateWorkspaceHandler(
|
||||
|
||||
public override async Task HandleAsync(CreateWorkspaceRequest request, CancellationToken ct)
|
||||
{
|
||||
if (!accessScopeService.IsManager(User))
|
||||
if (!await accessScopeService.CanCreateWorkspaceAsync(User, request.OrganizationId, ct))
|
||||
{
|
||||
await SendForbiddenAsync(ct);
|
||||
return;
|
||||
}
|
||||
|
||||
bool organizationExists = await dbContext.Organizations
|
||||
.AnyAsync(organization => organization.Id == request.OrganizationId, ct);
|
||||
if (!organizationExists)
|
||||
{
|
||||
AddError(request => request.OrganizationId, "The selected organization does not exist.");
|
||||
await SendErrorsAsync(StatusCodes.Status400BadRequest, ct);
|
||||
return;
|
||||
}
|
||||
|
||||
string normalizedName = request.Name.Trim();
|
||||
string normalizedSlug = request.Slug.Trim().ToLowerInvariant();
|
||||
string normalizedTimeZone = request.TimeZone.Trim();
|
||||
@@ -61,6 +72,7 @@ public class CreateWorkspaceHandler(
|
||||
Workspace workspace = new()
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
OrganizationId = request.OrganizationId,
|
||||
Name = normalizedName,
|
||||
Slug = normalizedSlug,
|
||||
OwnerUserId = User.GetUserId(),
|
||||
@@ -71,18 +83,7 @@ public class CreateWorkspaceHandler(
|
||||
dbContext.Workspaces.Add(workspace);
|
||||
await dbContext.SaveChangesAsync(ct);
|
||||
|
||||
WorkspaceDto dto = new(
|
||||
workspace.Id,
|
||||
workspace.Name,
|
||||
workspace.Slug,
|
||||
workspace.LogoUrl,
|
||||
workspace.TimeZone,
|
||||
workspace.ApprovalMode,
|
||||
workspace.SchedulePostsAutomaticallyOnApproval,
|
||||
workspace.LockContentAfterApproval,
|
||||
workspace.SendAutomaticApprovalReminders,
|
||||
[],
|
||||
workspace.CreatedAt);
|
||||
WorkspaceDto dto = WorkspaceDto.FromWorkspace(workspace, []);
|
||||
|
||||
await SendAsync(dto, StatusCodes.Status201Created, ct);
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ public class CreateWorkspaceInviteHandler(
|
||||
{
|
||||
Guid workspaceId = Route<Guid>("workspaceId");
|
||||
|
||||
if (!accessScopeService.CanManageWorkspace(User, workspaceId))
|
||||
if (!await accessScopeService.CanManageWorkspaceAsync(User, workspaceId, ct))
|
||||
{
|
||||
await SendForbiddenAsync(ct);
|
||||
return;
|
||||
|
||||
@@ -29,7 +29,7 @@ public class GetWorkspaceInvitesHandler(
|
||||
{
|
||||
Guid workspaceId = Route<Guid>("workspaceId");
|
||||
|
||||
if (!accessScopeService.CanManageWorkspace(User, workspaceId))
|
||||
if (!await accessScopeService.CanManageWorkspaceAsync(User, workspaceId, ct))
|
||||
{
|
||||
await SendForbiddenAsync(ct);
|
||||
return;
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Security.Claims;
|
||||
using Socialize.Api.Data;
|
||||
using Socialize.Api.Modules.Identity.Data;
|
||||
using Socialize.Api.Infrastructure.Security;
|
||||
using Socialize.Api.Modules.Workspaces.Data;
|
||||
|
||||
namespace Socialize.Api.Modules.Workspaces.Handlers;
|
||||
|
||||
@@ -12,6 +13,7 @@ public record WorkspaceMemberDto(
|
||||
string DisplayName,
|
||||
string Email,
|
||||
string? PortraitUrl,
|
||||
string RelationshipCategory,
|
||||
IReadOnlyCollection<string> Roles);
|
||||
|
||||
public class GetWorkspaceMembersHandler(
|
||||
@@ -29,12 +31,20 @@ public class GetWorkspaceMembersHandler(
|
||||
{
|
||||
Guid workspaceId = Route<Guid>("workspaceId");
|
||||
|
||||
if (!accessScopeService.CanManageWorkspace(User, workspaceId))
|
||||
if (!await accessScopeService.CanManageWorkspaceAsync(User, workspaceId, ct))
|
||||
{
|
||||
await SendForbiddenAsync(ct);
|
||||
return;
|
||||
}
|
||||
|
||||
Workspace? workspace = await dbContext.Workspaces
|
||||
.SingleOrDefaultAsync(candidate => candidate.Id == workspaceId, ct);
|
||||
if (workspace is null)
|
||||
{
|
||||
await SendNotFoundAsync(ct);
|
||||
return;
|
||||
}
|
||||
|
||||
string workspaceClaimValue = workspaceId.ToString();
|
||||
|
||||
var users = await dbContext.Users
|
||||
@@ -42,7 +52,11 @@ public class GetWorkspaceMembersHandler(
|
||||
dbContext.UserClaims.Any(claim =>
|
||||
claim.UserId == candidate.Id &&
|
||||
claim.ClaimType == KnownClaims.WorkspaceScope &&
|
||||
claim.ClaimValue == workspaceClaimValue))
|
||||
claim.ClaimValue == workspaceClaimValue) ||
|
||||
dbContext.OrganizationMemberships.Any(membership =>
|
||||
membership.UserId == candidate.Id &&
|
||||
membership.OrganizationId == workspace.OrganizationId) ||
|
||||
candidate.Id == workspace.OwnerUserId)
|
||||
.OrderBy(candidate => candidate.Lastname)
|
||||
.ThenBy(candidate => candidate.Firstname)
|
||||
.ThenBy(candidate => candidate.Email)
|
||||
@@ -70,12 +84,19 @@ public class GetWorkspaceMembersHandler(
|
||||
.ToArray(),
|
||||
ct);
|
||||
|
||||
HashSet<Guid> organizationMemberUserIds = await dbContext.OrganizationMemberships
|
||||
.Where(membership => membership.OrganizationId == workspace.OrganizationId)
|
||||
.Select(membership => membership.UserId)
|
||||
.ToHashSetAsync(ct);
|
||||
organizationMemberUserIds.Add(workspace.OwnerUserId);
|
||||
|
||||
var members = users
|
||||
.Select(candidate => new WorkspaceMemberDto(
|
||||
candidate.Id,
|
||||
BuildDisplayName(candidate),
|
||||
candidate.Email ?? string.Empty,
|
||||
candidate.PortraitUrl,
|
||||
organizationMemberUserIds.Contains(candidate.Id) ? "Organization Member" : "External Collaborator",
|
||||
rolesByUserId.GetValueOrDefault(candidate.Id) ?? Array.Empty<string>()))
|
||||
.ToList();
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ public record ApprovalStepConfigurationDto(
|
||||
|
||||
public record WorkspaceDto(
|
||||
Guid Id,
|
||||
Guid OrganizationId,
|
||||
string Name,
|
||||
string Slug,
|
||||
string? LogoUrl,
|
||||
@@ -28,7 +29,27 @@ public record WorkspaceDto(
|
||||
bool LockContentAfterApproval,
|
||||
bool SendAutomaticApprovalReminders,
|
||||
IReadOnlyCollection<ApprovalStepConfigurationDto> ApprovalSteps,
|
||||
DateTimeOffset CreatedAt);
|
||||
DateTimeOffset CreatedAt)
|
||||
{
|
||||
public static WorkspaceDto FromWorkspace(
|
||||
Workspace workspace,
|
||||
IReadOnlyCollection<ApprovalStepConfigurationDto> approvalSteps)
|
||||
{
|
||||
return new WorkspaceDto(
|
||||
workspace.Id,
|
||||
workspace.OrganizationId,
|
||||
workspace.Name,
|
||||
workspace.Slug,
|
||||
workspace.LogoUrl,
|
||||
workspace.TimeZone,
|
||||
workspace.ApprovalMode,
|
||||
workspace.SchedulePostsAutomaticallyOnApproval,
|
||||
workspace.LockContentAfterApproval,
|
||||
workspace.SendAutomaticApprovalReminders,
|
||||
approvalSteps,
|
||||
workspace.CreatedAt);
|
||||
}
|
||||
}
|
||||
|
||||
internal class GetWorkspacesHandler(
|
||||
AppDbContext dbContext,
|
||||
@@ -43,13 +64,9 @@ internal class GetWorkspacesHandler(
|
||||
|
||||
public override async Task HandleAsync(CancellationToken ct)
|
||||
{
|
||||
var query = dbContext.Workspaces.AsQueryable();
|
||||
|
||||
if (!accessScopeService.IsManager(User))
|
||||
{
|
||||
var workspaceScopeIds = User.GetWorkspaceScopeIds();
|
||||
query = query.Where(workspace => workspaceScopeIds.Contains(workspace.Id));
|
||||
}
|
||||
IReadOnlyCollection<Guid> accessibleWorkspaceIds = await accessScopeService.GetAccessibleWorkspaceIdsAsync(User, ct);
|
||||
var query = dbContext.Workspaces
|
||||
.Where(workspace => accessibleWorkspaceIds.Contains(workspace.Id));
|
||||
|
||||
var workspaceRows = await query
|
||||
.OrderBy(workspace => workspace.Name)
|
||||
@@ -71,18 +88,9 @@ internal class GetWorkspacesHandler(
|
||||
.ToArray());
|
||||
|
||||
var workspaces = workspaceRows
|
||||
.Select(workspace => new WorkspaceDto(
|
||||
workspace.Id,
|
||||
workspace.Name,
|
||||
workspace.Slug,
|
||||
workspace.LogoUrl,
|
||||
workspace.TimeZone,
|
||||
workspace.ApprovalMode,
|
||||
workspace.SchedulePostsAutomaticallyOnApproval,
|
||||
workspace.LockContentAfterApproval,
|
||||
workspace.SendAutomaticApprovalReminders,
|
||||
approvalStepsByWorkspaceId.GetValueOrDefault(workspace.Id) ?? Array.Empty<ApprovalStepConfigurationDto>(),
|
||||
workspace.CreatedAt))
|
||||
.Select(workspace => WorkspaceDto.FromWorkspace(
|
||||
workspace,
|
||||
approvalStepsByWorkspaceId.GetValueOrDefault(workspace.Id) ?? Array.Empty<ApprovalStepConfigurationDto>()))
|
||||
.ToList();
|
||||
|
||||
await SendOkAsync(workspaces, ct);
|
||||
|
||||
@@ -73,7 +73,7 @@ public class UpdateWorkspaceHandler(
|
||||
return;
|
||||
}
|
||||
|
||||
if (!accessScopeService.CanManageWorkspace(User, workspace.Id))
|
||||
if (!await accessScopeService.CanManageWorkspaceAsync(User, workspace.Id, ct))
|
||||
{
|
||||
await SendForbiddenAsync(ct);
|
||||
return;
|
||||
@@ -154,18 +154,7 @@ public class UpdateWorkspaceHandler(
|
||||
step.CreatedAt))
|
||||
.ToListAsync(ct);
|
||||
|
||||
WorkspaceDto dto = new(
|
||||
workspace.Id,
|
||||
workspace.Name,
|
||||
workspace.Slug,
|
||||
workspace.LogoUrl,
|
||||
workspace.TimeZone,
|
||||
workspace.ApprovalMode,
|
||||
workspace.SchedulePostsAutomaticallyOnApproval,
|
||||
workspace.LockContentAfterApproval,
|
||||
workspace.SendAutomaticApprovalReminders,
|
||||
approvalSteps,
|
||||
workspace.CreatedAt);
|
||||
WorkspaceDto dto = WorkspaceDto.FromWorkspace(workspace, approvalSteps);
|
||||
|
||||
await SendOkAsync(dto, ct);
|
||||
}
|
||||
|
||||
@@ -16,7 +16,8 @@ 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.Organizations;
|
||||
using Socialize.Api.Modules.Workspaces;
|
||||
|
||||
|
||||
@@ -62,9 +63,10 @@ var postgresConnectionString = builder.Configuration.GetConnectionString("Postgr
|
||||
builder.Services.AddAppData(postgresConnectionString);
|
||||
builder.AddInfrastructureModule();
|
||||
builder.AddIdentityModule();
|
||||
builder.AddOrganizationsModule();
|
||||
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>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user