Add calendar integrations and collaboration updates
Some checks failed
Backend CI/CD / build_and_deploy (push) Has been cancelled
Frontend CI/CD / build_and_deploy (push) Has been cancelled

This commit is contained in:
2026-05-05 15:25:53 -04:00
parent c49f03ec06
commit b66c10b681
82 changed files with 8420 additions and 2048 deletions

View File

@@ -0,0 +1,132 @@
using FastEndpoints;
using Microsoft.EntityFrameworkCore;
using Socialize.Api.Data;
using Socialize.Api.Infrastructure.Security;
using Socialize.Api.Modules.CalendarIntegrations.Data;
using Socialize.Api.Modules.CalendarIntegrations.Services;
using Socialize.Api.Modules.Organizations.Services;
namespace Socialize.Api.Modules.CalendarIntegrations.Handlers;
public class CreateCalendarSourceHandler(
AppDbContext dbContext,
AccessScopeService accessScopeService,
OrganizationAccessService organizationAccessService)
: Endpoint<UpsertCalendarSourceRequest, CalendarSourceDto>
{
public override void Configure()
{
Post("/api/calendar-integrations/sources");
Options(o => o.WithTags("Calendar Integrations"));
}
public override async Task HandleAsync(UpsertCalendarSourceRequest request, CancellationToken ct)
{
ArgumentNullException.ThrowIfNull(request);
Guid currentUserId = User.GetUserId();
string scope = request.Scope.Trim();
Guid? organizationId = request.OrganizationId;
Guid? workspaceId = request.WorkspaceId;
if (!await CanCreateAsync(scope, organizationId, workspaceId, currentUserId, ct))
{
await SendForbiddenAsync(ct);
return;
}
string? sourceUrl = NormalizeOptional(request.SourceUrl);
string? catalogSourceReference = NormalizeOptional(request.CatalogSourceReference);
if (await SourceAlreadyExistsAsync(scope, organizationId, workspaceId, currentUserId, sourceUrl, catalogSourceReference, ct))
{
AddError(request => request.SourceUrl, "This calendar source has already been added.");
await SendErrorsAsync(cancellation: ct);
return;
}
CalendarSource source = new()
{
Id = Guid.NewGuid(),
Scope = scope,
OrganizationId = scope == CalendarSourceScopes.Organization ? organizationId : null,
WorkspaceId = scope == CalendarSourceScopes.Workspace ? workspaceId : null,
UserId = scope == CalendarSourceScopes.User ? currentUserId : null,
SourceUrl = sourceUrl,
CatalogSourceReference = catalogSourceReference,
DisplayTitle = request.DisplayTitle.Trim(),
Color = request.Color.Trim(),
Category = request.Category.Trim(),
IsEnabled = request.IsEnabled,
InheritanceMode = scope == CalendarSourceScopes.Organization
? NormalizeOptional(request.InheritanceMode) ?? CalendarSourceInheritanceModes.Optional
: null,
UpdatedAt = DateTimeOffset.UtcNow,
};
dbContext.CalendarSources.Add(source);
await dbContext.SaveChangesAsync(ct);
await SendAsync(CalendarSourceDto.FromSource(source, isReadOnly: false), StatusCodes.Status201Created, ct);
}
private async Task<bool> CanCreateAsync(
string scope,
Guid? organizationId,
Guid? workspaceId,
Guid currentUserId,
CancellationToken ct)
{
return scope switch
{
CalendarSourceScopes.Organization when organizationId.HasValue =>
await dbContext.Organizations.AnyAsync(organization => organization.Id == organizationId.Value, ct) &&
await organizationAccessService.HasOrganizationPermissionAsync(
User,
organizationId.Value,
OrganizationPermissions.ManageConnectors,
ct),
CalendarSourceScopes.Workspace when workspaceId.HasValue =>
await dbContext.Workspaces.AnyAsync(workspace => workspace.Id == workspaceId.Value, ct) &&
await accessScopeService.CanManageWorkspaceAsync(User, workspaceId.Value, ct),
CalendarSourceScopes.User => currentUserId != Guid.Empty,
_ => false,
};
}
private Task<bool> SourceAlreadyExistsAsync(
string scope,
Guid? organizationId,
Guid? workspaceId,
Guid currentUserId,
string? sourceUrl,
string? catalogSourceReference,
CancellationToken ct)
{
IQueryable<CalendarSource> query = dbContext.CalendarSources
.Where(source => source.Scope == scope);
query = scope switch
{
CalendarSourceScopes.Organization => query.Where(source => source.OrganizationId == organizationId),
CalendarSourceScopes.Workspace => query.Where(source => source.WorkspaceId == workspaceId),
CalendarSourceScopes.User => query.Where(source => source.UserId == currentUserId),
_ => query.Where(_ => false),
};
string? normalizedUrl = sourceUrl?.Trim();
string? normalizedCatalogReference = catalogSourceReference?.Trim();
return query.AnyAsync(source =>
(!string.IsNullOrWhiteSpace(normalizedCatalogReference) &&
source.CatalogSourceReference == normalizedCatalogReference) ||
(!string.IsNullOrWhiteSpace(normalizedUrl) &&
source.SourceUrl != null &&
source.SourceUrl.ToUpper() == normalizedUrl.ToUpper()),
ct);
}
private static string? NormalizeOptional(string? value)
{
return string.IsNullOrWhiteSpace(value) ? null : value.Trim();
}
}