feat: add organization domain foundation

This commit is contained in:
2026-05-04 16:15:53 -04:00
parent 802668fb0b
commit 7d3f495472
55 changed files with 2995 additions and 115 deletions

View File

@@ -11,6 +11,8 @@ using Socialize.Api.Modules.ContentItems.Data;
using Socialize.Api.Modules.Clients.Data;
using Socialize.Api.Modules.Notifications.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,6 +21,7 @@ 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");
@@ -117,6 +120,11 @@ public static class DevelopmentSeedExtensions
[
]);
await EnsureOrganizationDataAsync(
manager.Id,
dbContext,
cancellationToken);
await EnsureWorkspaceDataAsync(
manager.Id,
clientUser.Id,
@@ -224,6 +232,50 @@ public static class DevelopmentSeedExtensions
return user;
}
private static async Task EnsureOrganizationDataAsync(
Guid managerUserId,
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,
Slug = string.Empty,
CreatedAt = DateTimeOffset.UtcNow,
};
dbContext.Organizations.Add(organization);
}
organization.Name = "Northstar Collective";
organization.Slug = "northstar-collective";
organization.OwnerUserId = managerUserId;
OrganizationMembership? membership = await dbContext.OrganizationMemberships
.SingleOrDefaultAsync(
candidate => candidate.OrganizationId == OrganizationId && candidate.UserId == managerUserId,
cancellationToken);
if (membership is null)
{
membership = new OrganizationMembership
{
Id = Guid.Parse("99999999-9999-9999-9999-000000000001"),
OrganizationId = OrganizationId,
UserId = managerUserId,
Role = OrganizationRoles.Owner,
CreatedAt = DateTimeOffset.UtcNow,
};
dbContext.OrganizationMemberships.Add(membership);
}
membership.Role = OrganizationRoles.Owner;
await dbContext.SaveChangesAsync(cancellationToken);
}
private static async Task EnsureWorkspaceDataAsync(
Guid managerUserId,
Guid clientUserId,
@@ -248,6 +300,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);

View File

@@ -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)
{
@@ -53,4 +55,123 @@ public sealed class AccessScopeService
|| 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);
}
}